Invoke-PSUScript called from an API endpoint ignores scoped script permissions and requires a parent-scope automation permission ("The role specified does not have access to this resource")

Backlog

Invoke-PSUScript called from an API endpoint ignores scoped script permissions and requires a parent-scope automation permission ("The role specified does not have access to this resource")

avatar

------------------------------------------------------------

Product: PowerShell Universal

Version: 2026.3.3

Hosting: IIS

OS: Windows Server 2025

Database: SQL Server

------------------------------------------------------------

CONTEXT

The script is not run directly by the user. The user calls an authenticated API endpoint, and it is the endpoint code that runs the script with Invoke-PSUScript:

User (AD group member, role DLG-PSU_2424)

-> calls API endpoint (Authentication enabled, restricted to DLG-PSU_2424)

-> endpoint code runs Invoke-PSUScript

-> script Add-UserToADGroup.ps1

Endpoint code (simplified):

param($Param1)

Invoke-PSUScript -Name '<Folder>\Add-UserToADGroup\Add-UserToADGroup.ps1' -Parameters $Param -Wait

The endpoint itself is reachable and executes correctly: the access denied error is raised by the Invoke-PSUScript call inside the endpoint. Replacing the Invoke-PSUScript line with $ClaimsPrincipal returns the expected identity, role and permissions (see EVIDENCE below), and replacing it with Get-PSUScript on the same script succeeds.

------------------------------------------------------------

SUMMARY

When a custom role is granted scoped permissions on a single script (or even the global automation.scripts/execute permission), calling Invoke-PSUScript from an API endpoint fails with:

"Permission denied. The role specified does not have access to this resource"

Get-PSUScript on the same script, from the same endpoint, succeeds with the same role, which shows that the scoped permission is matched correctly for read access. The call only succeeds when a parent-scope permission is added to the role: either automation/read or automation/execute. Both grant access to all automation resources, which defeats folder/script-level delegation.

EXPECTED BEHAVIOR

A role with automation.scripts/view + read + execute permissions scoped to a single script should be able to run that script through Invoke-PSUScript from an API endpoint, without needing access to the whole automation scope.

ACTUAL BEHAVIOR

Invoke-PSUScript, called from the API endpoint, is denied unless the role has automation/read or automation/execute.

------------------------------------------------------------

CONFIGURATION

Role: DLG-PSU_2424

- Claim type: http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid

- Claim value: AD group SID

- Role permissions: automation.scripts/view

Scoped permissions (created from the script's Permissions dialog, assigned to the role):

- automation.scripts.<Folder>\Add-UserToADGroup\Add-UserToADGroup.ps1/read

- automation.scripts.<Folder>\Add-UserToADGroup\Add-UserToADGroup.ps1/execute

API endpoint:

- Authentication: enabled

- Roles: DLG-PSU_2424, DLG-PSU_Administrator

- Execution roles: DLG-PSU_2424, DLG-PSU_Administrator

------------------------------------------------------------

EVIDENCE: CLAIMS ARE CORRECTLY RESOLVED

Returning $ClaimsPrincipal from the endpoint (called by a member of the AD group) shows:

Type : http://schemas.microsoft.com/ws/2008/06/identity/claims/role

Value : DLG-PSU_2424

Type : PSUPermission

Value : automation.scripts/view

Type : PSUPermission

Value : automation.scripts.<Folder>\\Add-UserToADGroup\\Add-UserToADGroup.ps1(.*)/read

Type : PSUPermission

Value : automation.scripts.<Folder>\\Add-UserToADGroup\\Add-UserToADGroup.ps1(.*)/execute

The role and permissions are correctly present in the identity. Cached claims were ruled out (new sign-in after every change).

------------------------------------------------------------

STEPS TO REPRODUCE

1. Create a custom role with only automation.scripts/view.

2. Create a script in a subfolder, open its Permissions dialog and grant read and execute to the role.

3. Create an authenticated API endpoint restricted to that role, containing:

Get-PSUScript -Name '<Folder>\Add-UserToADGroup\Add-UserToADGroup.ps1' # Test A

Invoke-PSUScript -Name '<Folder>\Add-UserToADGroup\Add-UserToADGroup.ps1' # Test B

4. Call the endpoint as a user holding only that role.

------------------------------------------------------------

TEST MATRIX (all tests run from inside the API endpoint)

Additional permissions on the role Get-PSUScript Invoke-PSUScript

-------------------------------------------------------------------------------------------

1. none OK DENIED

2. none, with -Wait OK DENIED

3. none, with -Integrated OK DENIED

4. automation.jobs/read OK DENIED

5. automation.scripts/execute + automation.jobs/create OK DENIED

6. automation.environments/read OK DENIED

7. automation.jobs/* OK DENIED

8. automation.computers/read OK DENIED

9. automation/read OK OK

10. automation/execute OK OK

11. automation/* OK OK

------------------------------------------------------------

ANALYSIS

- The scoped permission pattern is matched correctly for read (test A succeeds from the endpoint).

- The failure is not related to jobs, environments or computers (tests 4, 6 to 8).

- Even the global automation.scripts/execute is not sufficient (test 5).

- Any permission at the parent automation level, regardless of access type (read or execute), unlocks the call (tests 9 and 10).

This suggests that the authorization check performed by Invoke-PSUScript, when called from an API endpoint, evaluates a resource at the automation scope level instead of automation.scripts.<path>, so scoped permissions are never considered.

------------------------------------------------------------

IMPACT

Folder- and script-scoped delegation (as described in the Permissions documentation) cannot be used for scripts invoked from API endpoints. The only working workaround is granting automation/read or automation/execute, which exposes or allows execution of every script on the instance, including scripts running under privileged credentials.

All Comments (2)

avatar

Hello Dylan,

Thank you for the detailed report and the test matrix, it made the analysis much faster.

We reproduced the issue in our lab on PowerShell Universal 2026.3.3 on Windows.

Invoke-PSUScript checks your permissions on the script three times: when it looks the script up by name, when it reads the script's parameter list, and when it starts the job.

On Windows, the parameter list check writes the script path with forward slashes, while the other two checks use backslashes.

Your scoped permissions use backslashes, so the parameter list check never matches them for a script in a subfolder.

That is why a parent permission such as automation/read unlocks the call.

Scripts at the root of the Scripts list are not affected, because their path has no folder separator.

I have escalated this to our development team with your test matrix and our reproduction steps.

In the meantime, you can keep the delegation limited to this one script by adding a second Read permission, written with forward slashes, to the role DLG-PSU_2424.

In .universal/roles.ps1, add it to the Permissions list of the DLG-PSU_2424 role, next to automation.scripts/view.

-Permissions @('automation.scripts/view', 'automation.scripts.<Folder>/Add-UserToADGroup/Add-UserToADGroup.ps1/read')

Keep your two existing scoped permissions, because the lookup by name and the job start still need the backslash form.

Please do not recreate them from the script's Permissions dialog, since in 2026.3.3 the dialog saves the forward-slash form, which the other two checks reject.

In our lab, this ran the script from an authenticated endpoint with and without -Wait, with no other permission on the role.

You can remove the extra entry once the fix is installed.

If you prefer not to change the role, two other setups also worked in our lab.

Call Invoke-PSUScript with -AppToken and -ComputerName, using an app token stored as a secret whose role holds automation.scripts/read and an Execute permission in the same backslash form you use today, so your users only need access to the endpoint.

Move Add-UserToADGroup.ps1 to the root of the Scripts list and recreate its Read and Execute permissions on the new path.

App tokens: https://docs.devolutions.net/powershell-universal/security/app-tokens

One detail would help the developers: in test 10, had automation/read already been removed from the role when you added automation/execute?

In our lab, automation/execute alone was still denied, so your answer tells us whether your environment behaves differently.

Best regards,

Patrick Ouimet

avatar

Hi,

Thanks for the workaround. It also resolves the issue on my end when setting the permissions in `roles.ps1`.

To answer your question, the `automation/read` permission had not been removed at the time of the test.

Thank you very much for your support.