skills/using-security-apis-and-role-escalation/SKILL.md
# Using Security APIs and Role Escalation Starting with InterSystems IRIS 2025.2, all security APIs require the following privileges: - %DB_IRISSYS:R - %Admin_Secure:U The OAuth2 classes also require one of the following, depending on the class and operation: - %Admin_OAuth2_Client:U— Creating and using InterSystems IRIS as a client or resource server. - %Admin_OAuth2_Server:U — Configuring and using InterSystems IRIS as an authorization serv er. - %Admin_OAuth2_Registration:U — Revoking
npx skillsauth add sorodriguezz/skills-objectscript skills/using-security-apis-and-role-escalationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Starting with InterSystems IRIS 2025.2, all security APIs require the following privileges:
%DB_IRISSYS:R
%Admin_Secure:U
The OAuth2 classes also require one of the following, depending on the class and operation:
%Admin_OAuth2_Client:U— Creating and using InterSystems IRIS as a client or resource server.
%Admin_OAuth2_Server:U — Configuring and using InterSystems IRIS as an authorization serv er.
%Admin_OAuth2_Registration:U — Revoking access tokens and registering clients for an InterSystems IRIS authorization server.
If a security API is called without these privileges, an Access Denied error is returned to the caller, and the attempt is recorded in the audit log as a %System/%Security/AccessDenied event.
You can get the required privileges in the following ways:
Role assignment — Owning a role that has the privilege. The following is a list of each privilege and the predefined roles that have them:
–
–
– %DB_IRISSYS:R — %DB_IRISSYS_READ, %Manager %Admin_Secure:U — %Admin_Secure, %SecurityAdministrator
%Admin_OAuth2_*:U — %SecurityAdministrator
Role escalation for IRISSYS routines — Executing a % routine or class which physically resides in the IRISSYS database and sets $ROLES directly.
Application or matching roles escalation
%All role — Having the %All role gives this (and all other) privileges.
The second method, role escalation, is the most versatile and is the primary subject of this article.
A process can modify its privileges through role escalation by adding a role to its $ROLES variable (for example, the predefined %DB_IRISSYS_READ and %Admin_Secure roles) if any of the following are true:
The method or routine resides in the IRISSYS database.
The method or routine is mapped to the IRISSYS database.
Role Escalation
As a best practice, you should also use NEW $ROLES to ensure that the role escalation only lasts until the end of the method or routine.
For example, a method can add use the role NewRole until the end of its call with the following:
ObjectScript
NEW $ROLES SET $ROLES=$ROLES_",NewRole"
You can use this to escalate roles for the relevant security APIs. For example, to escalate to the predefined role %Admin_Secure:
ObjectScript
NEW $ROLES SET $ROLES=$ROLES_",%Admin_Secure"
In this example, the routine SECURITYAPI is in the USER namespace and resides in the USER database (IRISinstallDir\mgr\user). AddRole() attempts to modify the $ROLES value to escalate to %Admin_Secure:
ObjectScript
AddRole() public { WRITE !,"$ROLES="$ROLES SET $ROLES=$ROLES",%Admin_Secure" WRITE !,"$ROLES="_$ROLES QUIT }
However, executing this routine results in a PROTECT error because the routine does not reside in and is not mapped to the IRISSYS database, which are the prerequisites for SET $ROLES:
USER>DO AddRole^SECURITYAPI $ROLES=%Developer,%DB_USER SET $ROLES=$ROLES_",%Admin_Secure" ^ <PROTECT>SECURITYAPI+5^SECURITYAPI USER 2d0>
To fix this, you can either mo ve or map the routine to the IRISSYS database. Classes and routines that start with %Z or %z are automatically mapped to the IRISSYS database.
This example uses the mapping method, changing the name of the routine to %ZSECURITYAPI, which maps it to the IRISSYS database. This allows the process to run % routines and classes:
USER>DO AddRole^%ZSECURITYAPI $ROLES=%Developer,%DB_USER $ROLES=%Developer,%Admin_Secure,%DB_USER USER>WRITE $ROLES %Developer,%Admin_Secure,%DB_USER
For completeness, this is the same routine written in a %Z class:
Class Definition
Class %ZUSER.Security [ Abstract ] { ClassMethod AddRole() { WRITE !,"$ROLES="$roles SET $ROLES=$ROLES",%Admin_Secure" WRITE !,"$ROLES="_$ROLES QUIT } }
Again, the class name starts with %Z, so it's automatically mapped to the IRISSYS database:
USER>DO ##Class(%ZUSER.Security).AddRole() $ROLES=%Developer,%DB_USER $ROLES=%Developer,%Admin_Secure,%DB_USER USER>WRITE $ROLES %Developer,%Admin_Secure,%DB_USER
To make role escalation more convenient, you can define macros in an include file . In this example, the include file is called %ZUSERSecurity.inc:
ObjectScript
; Escalates to %DB_IRISSYS_READ, giving the caller %IRISSYS:R #Define AddDBIRISSYSRead NEW $ROLES SET $ROLES=$ROLES_", %DB_IRISSYS_READ" ; Escalates to %Admin_Secure, giving the caller %Admin_Secure:U #Define AddAdminSecure NEW $ROLES SET $ROLES=$ROLES_",%Admin_Secure"
Building on the previous example class %ZUSER.Security, you can add the following to make it more idiomatic:
Add the include file (in this case, %ZUSERSecurity.inc)
Add TRY/CATCH error handling.
Verify that the caller has the privileges to call the method.
Restrict which classes and routines can call the method.
By default, any process in the system can call %Z and %z classes directly. To restrict this, you can add checks to the beginning of the method to verify that the caller fulfills the specified requirements:
should only explicitly check for permissions on user-defined resources; predefined resources can potentially change between InterSystems IRIS versions.
%SYSTEM.Process.CallingDatabase() — Returns the directory name of the calling routine's database.
%SYSTEM.Process.CallingRoutine() — Returns the name of the calling routine.
In general, you should use at least one of these checks for methods and routines that read or modify security data.
For example, you can add the following line to the beginning of a method to verify that the calling user has the user-defined APPSECURITY:U privilege:
ObjectScript
if '$SYSTEM.Security.Check("APPSECURITY","USE") QUIT $$$ERROR($$$AccessDenied)
Similarly, suppose you're running the class USER.APP in the USER namespace and want to call the method GetUsernameInfo():
Class Definition
Class USER.APP [ Abstract ] { ClassMethod GetUsernameInfo(Username as %String, byRef Properties as %String) As %Status { QUIT ##Class(%ZUSER.Security).GetUsernameInfo(Username,.Properties) } }
You can modify the method as follows to test the calling database and routine and verify it can only be called from the USER.APP class in the USER database:
Class Definition
Include (%ZUSERSecurity, %occErrors) Class %ZUSER.Security [ Abstract ] { ClassMethod GetUsernameInfo(Username As %String, ByRef UserInfo As %String) As %Status { TRY { #;Calling user must have the APPSECURITY:USE privilege IF '$SYSTEM.Security.Check("APPSECURITY","USE") $$$ThrowStatus($$$ERROR($$$AccessDenied))
#;Calling class must be USER.APP and physically located in the USER database IF ($SYSTEM.Process.CallingRoutine()'="USER.APP.1") || ($SYSTEM.Process.CallingDatabase()'="/iris/mgr/user/") $$$ThrowStatus($$$ERROR($$$AccessDenied))
#;Required to change to %SYS namespace to use APIs $$$AddDBIRISSYSRead
#;Required to access security APIs $$$AddAdminSecure
#;On method exit, restore to calling namespace NEW $Namespace SET $Namespace="%SYS"
$$$ThrowOnError(##Class(Security.Users).Get(Username,.UserInfo)) } CATCH Exception { RETURN Exception.AsStatus() } QUIT $$$OK } }
Here, GetUsernameInfo() shows that the user Bob is enabled:
USER>SET Status=##Class(USER.APP).GetUsernameInfo("Bob",.UserInfo) USER>WRITE UserInfo("Enabled") UserInfo("Enabled")=1
The following method disables a user. Before performing role escalation with the $$$AddDBIRISSYSREAD and $$$AddAdminSecure macros, it verifies that the calling user has APPSECURITY:U and that the calling routine/method is either USER.APP.1 or exists in the USER database:
ObjectScript
ClassMethod DisableUser(Username As %String) As %Status { TRY { IF '$SYSTEM.Security.Check("APPSECURITY","USE") $$$ThrowStatus($$$ERROR($$$AccessDenied)) IF ($SYSTEM.Process.CallingRoutine()'="USER.APP.1") || ($SYSTEM.Process.CallingDatabase()'="/iris/mgr/user/") $$$ThrowStatus($$$ERROR($$$AccessDenied)) $$$AddDBIRISSYSRead $$$AddAdminSecure NEW $Namespace SET $Namespace="%SYS" SET Properties("Enabled")=0 $$$ThrowOnError(##Class(Security.Users).Modify(Username,.Properties)) } CATCH Exception { RETURN Exception.AsStatus() } QUIT $$$OK }
To disable the user Bob:
ObjectScript
USER>SET Status=##Class(USER.APP).DisableUser("Bob") USER>WRITE Status 1 USER>DO ##Class(%ZUSER.Security).GetUsernameInfo("Bob",.UserInfo) USER>WRITE UserInfo("Enabled") 0
This next example creates a new role OAuthRole which has the following privileges:
%Admin_OAuth2_Client:U
%Admin_OAuth2_Server:U
%Admin_OAuth2_Registration:U
ObjectScript
ClassMethod CreateOAuthRole() As %Status { TRY { IF '$SYSTEM.Security.Check("APPSECURITY","USE") $$$ThrowStatus($$$ERROR($$$AccessDenied)) IF ($SYSTEM.Process.CallingRoutine()'="USER.APP.1") || ($SYSTEM.Process.CallingDatabase()'="/iris/mgr/user/") $$$ThrowStatus($$$ERROR($$$AccessDenied)) $$$AddDBIRISSYSRead $$$AddAdminSecure NEW $Namespace SET $Namespace="%SYS" $$$ThrowOnError(##Class(Security.Roles).Create("OAuthRole","OAuth privilege role", "%Admin_OAuth2_Client:Use,%Admin_OAuth2_Server:Use,%Admin_OAuth2_Registration:Use")) RETURN $$$OK } CATCH Exception { RETURN Exception.AsStatus() } }
For convenience, you can create a macro to escalate to the new OAuthRole:
ObjectScript
#Define AddOAuthRole New $Roles Set $Roles=$Roles_",OAuthRole"
Like globals, without %All, security tables are restricted and can only be accessed through security APIs.
You can query security tables with the following:
Query API
Embedded and Dynamic SQL
The Query API lets you query security tables with the following:
Predefined Query methods
Custom queries with Security.SQLQuery Like all other security APIs and classes, these require %DB_IRISSYS:R and %Admin_Secure:U.
The following example uses a predefined Query method. To get a list of roles, instead of querying the Security.Roles table, it uses the predefined Security.Roles_List() query:
SELECT Name,Description FROM Security.Roles_List()
The following example runs the Security.Users_List() to return the contents of the Enabled column:
ObjectScript
ClassMethod GetUsersEnabledState(ByRef Users As %String) As %Status { TRY { IF '$SYSTEM.Security.Check("APPSECURITY","USE") $$$ThrowStatus($$$ERROR($$$AccessDenied)) IF ($SYSTEM.Process.CallingRoutine()'="USER.APP.1") || ($SYSTEM.Process.CallingDatabase()'="/iris/mgr/user/") $$$ThrowStatus($$$ERROR($$$AccessDenied)) $$$AddDBIRISSYSRead $$$AddAdminSecure NEW $Namespace SET $Namespace="%SYS" SET stmt = ##class(%SQL.Statement).%New() $$$ThrowOnError(stmt.%PrepareClassQuery("Security.Users","List",0)) SET rs = stmt.%Execute("*").%NextResult() IF rs.%SQLCODE < 0 $$$ThrowStatus($$$ERROR($$$SQLError, rs.%SQLCODE, rs.%Message)) WHILE rs.%Next() { SET Users(rs.Name)=rs.Enabled } RETURN $$$OK } CATCH Exception { RETURN Exception.AsStatus() } }
If you want to create a custom query instead of using the predefined ones, you can create a class query with class Security.SQLQuery. For example:
ObjectScript
Query ListEnabled(ROWSPEC = "Name:%String,Enabled:%Boolean") As Security.SQLQuery [ SqlProc ] { SELECT Name,Enabled FROM Security.Users ORDER BY Name }
You can then use the custom query in your methods. For example, GetUsersEnabledStateSQLQuery() calls ListEnabled. Because this query, like all other security APIs, requires elevated privileges, this method and performs checks its callers before escalating to %DB_IRISSYS_READ and %Admin_Secure:
ObjectScript
ClassMethod GetUsersEnabledStateSQLQuery(ByRef Users As %String) As %Status { TRY { IF '$SYSTEM.Security.Check("APPSECURITY","USE") $$$ThrowStatus($$$ERROR($$$AccessDenied)) IF ($SYSTEM.Process.CallingRoutine()'="USER.APP.1") || ($SYSTEM.Process.CallingDatabase()'="/iris/mgr/user/") $$$ThrowStatus($$$ERROR($$$AccessDenied)) $$$AddDBIRISSYSRead $$$AddAdminSecure NEW $Namespace SET $Namespace="%SYS" SET stmt = ##class(%SQL.Statement).%New() $$$ThrowOnError(stmt.%PrepareClassQuery("%ZUSER.Security","ListEnabled",0)) SET rs = stmt.%Execute().%NextResult() IF rs.%SQLCODE < 0 $$$ThrowStatus($$$ERROR($$$SQLError, rs.%SQLCODE, rs.%Message)) WHILE rs.%Next() { SET Users(rs.Name)=rs.Enabled } RETURN $$$OK } CATCH Exception {
RETURN Exception.AsStatus() } }
While you can query the security database with Embedded and Dynamic SQL, this accesses security globals directly (as opposed to the provided APIs); this form of direct access requires %All, which is more dangerous and not recommended.
The following examples use a macro to escalate to %All:
ObjectScript
#define AddAllRole New $Roles Set $Roles=$Roles_",%All"
To return the contents of the Enabled column with embedded SQL:
ObjectScript
ClassMethod GetUsersEnabledStateEmbeddedSQL(ByRef Users As %String) As %Status { TRY { IF '$SYSTEM.Security.Check("APPSECURITY","USE") $$$ThrowStatus($$$ERROR($$$AccessDenied)) IF ($SYSTEM.Process.CallingRoutine()'="USER.APP.1") || ($SYSTEM.Process.CallingDatabase()'="/iris/mgr/user/") $$$ThrowStatus($$$ERROR($$$AccessDenied)) $$$AddAllRole NEW $Namespace SET $Namespace="%SYS" &sql(DECLARE C1 CURSOR FOR SELECT Name,Enabled INTO :name,:enabled FROM Security.Users ORDER BY Name) &sql(OPEN C1) QUIT:(SQLCODE'=0) &sql(FETCH C1) WHILE (SQLCODE = 0) { SET Users(name)=enabled &sql(FETCH C1) }
&sql(CLOSE C1) RETURN $$$OK } CATCH Exception { RETURN Exception.AsStatus() } }
Similarly, to return the contents of the Enabled column with dynamic SQL:
ClassMethod GetUsersEnabledStateDynamicSQL(ByRef Users As %String) As %Status { TRY { IF '$SYSTEM.Security.Check("APPSECURITY","USE") $$$ThrowStatus($$$ERROR($$$AccessDenied)) IF ($SYSTEM.Process.CallingRoutine()'="USER.APP.1") || ($SYSTEM.Process.CallingDatabase()'="/iris/mgr/user/") $$$ThrowStatus($$$ERROR($$$AccessDenied)) $$$AddAllRole NEW $Namespace SET $Namespace="%SYS" SET query="SELECT * FROM Security.Users" SET rs=##class(%SQL.Statement).%ExecDirect(,query) IF (rs.%SQLCODE < 0) $$$ThrowStatus($$$ERROR($$$SQLError, rs.%SQLCODE, rs.%Message)) WHILE rs.%Next(.sc) { Set Users(rs.%Get("Name"))=rs.%Get("Enabled") } RETURN $$$OK } CATCH Exception { RETURN Exception.AsStatus() } }
data-ai
# XDBC Gateway XDBC Gateway Overview The XDBC Gateway provides a single interface for connecting InterSystems IRIS® to an external database with JDBC or ODBC. It acts as the modern, streamlined successor to the SQL Gateway. The XDBC Gateway acts like a JDBC or ODBC driver and lets you perform any of the following operations: - Access data stored in third-party relational databases within InterSystems IRIS applications. - Store persistent InterSystems IRIS objects in external relational data
devops
# Windows Installation Guide Windows Installation Overview The Windows Installation Guide provides guidance on installing kit-based deployments on Microsoft Windows. ### 1.1 How to Use This Guide For all installations, you should begin with the Pre-Installation steps. You can then follow the steps for either an attended or unattended installation. The attended installation process is different depending on the setup type you choose. After following the steps for attended installations, use t
testing
# Windows Installation Details Windows User Accounts When installing InterSystems IRIS, you must choose the Windows user account to run the InterSystems service, InterSystems IRIS Controller for <instance-name>. There are two options: - The default SYSTEM account (Windows Local System account). This is used in Minimal security installations. - A defined Windows user account. This account must have interactive login privileges for the duration of the installation; they can be revoked after.
tools
# Web Gateway Guide C.2.1 Method 1: Building the CSP Module as Shared Object with apxs (APache eXtenSion) Appendix D: Add the Web Gateway to a Locked-Down Apache Installation (UNIX®/Linux/macOS) The Web Gateway: Serve InterSystems Web Applications and REST APIs to a Web Client An InterSystems IRIS® web application consists of code which provides content dynamically to a web client (usually a web browser) in response to a request. The InterSystems Web Gateway makes this possible: it is a soft