PathixDataverse Forensics

← Guides

Guide · Security

The integration account problem in Dataverse

Service accounts gain privilege the way anything does: one deadline at a time. Here are the four routes it usually takes, and how to find each of them in an environment you inherited.

Updated 26 July 202610 min read

Nobody sets out to give an integration System Administrator. It happens because a data sync fails at five on a Friday, the error is an access denied nobody can place, and the fastest path to a working integration is the role that bypasses the question. The ticket closes. The role stays.

Multiply that by six years and a dozen integrations and you get the state most mature Dynamics environments are actually in. This is how to find it.

What an application user is

An application user is a systemuser row with an applicationid set, bound to an Entra app registration rather than a person. It cannot log in interactively. It authenticates as a service and acts through the API.

Everything that makes it convenient also makes it a good target. It has no interactive login to protect with MFA, its credential is a secret in someone's configuration, it usually runs unattended, and nobody notices when it does something unusual because nobody is watching a service account's activity the way they watch a person's.

A stolen integration credential inherits every privilege the account holds, and none of the friction a stolen human login would meet.

Which makes the size of that privilege set the whole question.

Route one: the system role

The most common and the most serious. An application user holding System Administrator or System Customizer bypasses virtually every row-level and privilege check in the platform. A compromised credential on that account can read every record, change the security model, delete data, and clean up after itself.

External testers find this pattern inside the first fifteen minutes of an engagement, because it is fast to check and it is very often there.

Finding it

List every application user with its roles. This FetchXML runs anywhere you can execute one, including the browser tools and the standard admin utilities:

every application user and the roles it holds
<fetch>
  <entity name="systemuser">
    <attribute name="fullname" />
    <attribute name="applicationid" />
    <filter>
      <condition attribute="applicationid" operator="not-null" />
    </filter>
    <link-entity name="systemuserroles" from="systemuserid" to="systemuserid">
      <link-entity name="role" from="roleid" to="roleid">
        <attribute name="name" />
      </link-entity>
    </link-entity>
  </entity>
</fetch>

Read the result for anything named System Administrator or System Customizer. Then read it again for roles you do not recognise, because the second most common version of this problem is a custom role that was copied from System Administrator years ago and never trimmed.

Route two: the shared role

A single security role assigned to both people and integrations. It looks tidy. It means every privilege you add for one audience silently lands on the other.

The failure mode is subtle and it works in both directions. Someone widens the role so the support team can edit a new table, and an internet-facing integration quietly gains write access to it. Or the integration needs a new privilege, and forty people get it too.

Finding it

Take the query above and group by role rather than by user. Any role whose holders include both a row with applicationid set and a row without it is shared between humans and integrations. There is no built-in screen that shows you this, which is most of why it survives.

WORTH CHECKING EVEN WHEN IT LOOKS INTENTIONAL

Some shared roles are deliberate. The question is whether anyone decided that, or whether the role simply accumulated both audiences. If it was a decision, write it down somewhere the next person will find it.

Route three: access through a team

Roles reach principals through team membership as well as direct assignment, and the team route is much easier to lose track of.

  • A role-holding team whose membership mixes people and service accounts is the shared-role problem again, arriving where a direct-assignment check will not see it.
  • Every business unit has a default team, and every user in that business unit is an unremovable member of it. A role granted to a default team therefore grants to the whole business unit, and it widens automatically as people are added. This is easy to do by accident and almost impossible to notice afterwards.

Finding it

teams, the roles they hold, and who is in them
<fetch>
  <entity name="team">
    <attribute name="name" />
    <attribute name="teamtype" />
    <link-entity name="teamroles" from="teamid" to="teamid">
      <link-entity name="role" from="roleid" to="roleid">
        <attribute name="name" />
      </link-entity>
    </link-entity>
    <link-entity name="teammembership" from="teamid" to="teamid">
      <link-entity name="systemuser" from="systemuserid" to="systemuserid">
        <attribute name="fullname" />
        <attribute name="applicationid" />
      </link-entity>
    </link-entity>
  </entity>
</fetch>

Two things to read for. Teams whose members include both application users and humans. And any default team that holds a role at all, since the membership of those is not something you control.

Route four: privileges that rewrite the model

The quietest one. An account does not need System Administrator if it holds the privileges that let it grant itself System Administrator.

Role management, role assignment, user creation and team write privileges together mean an account can create a role, put whatever it likes in it, and assign it to itself. A compromised credential becomes full environment compromise through the API alone, without exploiting anything. No vulnerability is involved; the account was simply allowed to.

There is essentially no reason a production integration needs to modify security roles. If one genuinely manages user provisioning, that is usually a dedicated identity product rather than a general-purpose integration, and it deserves its own scrutiny.

The same reasoning applies to the delegation privilege that lets one principal act on behalf of another. It has legitimate uses, and every holder is worth confirming rather than assuming.

Finding it

This one is harder by hand, because you are looking at the privileges inside a role rather than the role itself. Export the roles held by your application users and inspect their privilege lists for anything touching role, user, or team management. In a large environment this is the point where the manual approach starts costing real time.

The gap that outlives everyone

Disabling a user account suspends interactive login. It does not revoke role assignments, and it does not revoke records that account shared with other people.

So a disabled account still holds its roles. Re-enable it, deliberately or through a directory sync that flips a flag, and everything reinstates at once with no further action. Manual record shares are worse, because they have no surface in the user interface at all, which means a careful offboarding can complete without anyone seeing them.

Automation owned by a departed person is the third version. A flow or workflow owned by a disabled account does not stop cleanly; it fails or refuses reactivation, usually months later, and the process it drove quietly stopped in the meantime.

Worth checking whenever an incident timeline lines up with somebody's last day.

What good looks like

None of this requires a rebuild. The target state is unglamorous.

  • One application user per integration, not one shared service account fronting several. Attribution is only useful when the identity names one thing, which matters the day you are reading an audit trail.
  • A dedicated least-privilege role per integration, scoped to the tables and operations it actually touches, and never shared with a human population.
  • No integration holding privileges over roles, users, or teams.
  • Role removal and share revocation in the offboarding runbook, so neither depends on somebody remembering.
  • Roles on owner teams rather than default teams, so membership is a decision.

Replacing a system role on a live integration is the one change that needs care. Deploy the replacement role alongside the old one, confirm the integration still works, then remove the system role. Doing it in the other order books you an outage.

Why audit attribution rarely names the component →

More guides →

Pathix answers this across the whole environment.

It parses the surfaces the platform stores but does not read: compiled plugin IL, classic-workflow XAML, flow definition JSON, form scripts, canvas PowerFx. Every edge records how it was resolved, and the writes it could not resolve get reported rather than quietly dropped. Self-hosted in your own Azure, read-only, metadata-only.

See it on sample data →How the scan works →
Pathix

Forensics for Dynamics 365 and the Dataverse.

See it on sample data →
USE CASES
CAPABILITIES
  • What we check
  • Dataverse MCPsoon
  • Dependency analysissoon
  • Migration impactsoon
PRODUCT
COMPANY
© 2026 Pathix · self-hosted · metadata-onlyNot affiliated with Microsoft. Dynamics 365, Dataverse, and Power Platform are trademarks of Microsoft Corporation.