23 deterministic security findings, each mapped to specific SOC 2, ISO 27001:2022, and NIST 800-53 controls, plus 17 operational findings for environment health. Every one is read from your real customization graph, plugins, roles, flows, and field security, and scored per instance in your own environment.
Each of these is a condition Pathix detects deterministically and maps to SOC 2, ISO 27001:2022, and NIST 800-53. The mappings are indicative: Pathix detects a condition relevant to a control, it does not certify your compliance, and a finding is not an audit opinion. You stay responsible for your control environment.
Who really holds what, and where a change or a stolen credential quietly widens access, including the self-elevation paths.
One role held by both people and service accounts means every privilege you add hits both audiences at once, and a stolen integration key inherits full human access.
Fix. Split the role in two: humans on one copy, integrations on the other.
A service account with System Administrator or System Customizer bypasses nearly every access check. A compromised key can read everything, rewrite security, and cover its tracks. Pentesters find this in the first fifteen minutes.
Fix. Replace the system role with a custom least-privilege role scoped to what the integration actually touches.
A service account whose role can create roles, assign roles, or provision users can bootstrap itself to full control through the API alone. No production integration needs this.
Fix. Remove the security-metadata write privileges; put any genuine user management behind human approval.
A non-admin whose custom role grants role or user administration can grant themselves anything through the API: a low-privilege login that can bootstrap itself to full control.
Fix. Remove those privileges, reserve admin for named people, and turn on prevent-elevation-of-privilege.
A role-holding team with both people and service accounts as members grants every privilege to both, the same shared-role risk arriving through team membership, where the direct check cannot see it.
Fix. Move integrations onto a dedicated team with its own least-privilege role.
Every user in a business unit is an unremovable member of its default team, so a role there grants to the whole unit and widens automatically as people are added. Easy to do by accident.
Fix. Move the role onto an owner team or an explicit assignment naming only the users who need it.
The delegation privilege lets a principal act as another user, attributing their actions to someone else and operating with that user's access. Legitimate for some integrations, worth confirming for the rest.
Fix. Confirm each holder needs it, scope it to a dedicated integration identity, and remove it elsewhere.
A plugin step runs on a CRUD message against a table whose privilege no operational role grants, so least-privilege users hit access errors and the step does not execute. A functional-coverage gap, not over-privilege.
Fix. Add the missing privilege to a role the users already hold, custom or out-of-the-box.
The canonical "termination did not fully revoke" family: every residual path a disabled account leaves behind. Pathix's strongest single compliance story.
Disabling a user suspends login but keeps their roles. Re-enable the account, on purpose or through a directory sync, and every role snaps back with no further action.
Fix. Strip roles during offboarding; re-grant on reactivation only after review.
Disabling a user revokes neither roles nor record shares, and shares have no surface in the user UI, so they survive careful offboarding. Re-enable the account and the shared records are reachable again.
Fix. Revoke manual shares alongside role removal, or reassign the records to a team.
A live flow or workflow owned by a disabled account authenticates and runs under that owner, so it stalls or refuses reactivation, and the process it drove silently stops.
Fix. Reassign to a service account, re-test its connections, and add reassignment to offboarding.
A step set to always run as a disabled user fails every time it fires, because Dataverse cannot build a run-as context for a disabled principal.
Fix. Repoint the impersonation to an active service account, or remove it so the step runs as the caller.
Every change the step makes is attributed to one account regardless of who triggered it, obscuring who really acted, and it breaks the day that account leaves.
Fix. Confirm the impersonation is intentional and points at a dedicated service account, not a person's login.
Where a field marked sensitive is misconfigured, exposed, or quietly copied past its own protection.
A field mapping or formula copies a secured value into an unsecured column, so anyone who can read the destination reads the protected value without a grant. An active leak of data you deliberately gated.
Fix. Secure the destination the same way or break the mapping, and clear values already copied.
A secured column sits in the default view, so its header shows to everyone and rows without a grant render blank. Dataverse still enforces access, so this is a posture and UX gap, not a leak.
Fix. Remove the secured column from the default view; make a separate view for the audience that has the grant.
Same problem on a form: users without the grant see a blank control that reads as broken rather than protected. Enforced server-side, so posture and UX, not exfiltration.
Fix. Remove the secured column from forms shown to audiences that lack the grant, or scope a form variant to those who have it.
A column is secured but no field-security profile grants access, so it is unreachable in the UI and SDK. Users hit Access Denied mid-workflow. A functional gap, mapped lightly.
Fix. Grant the column through a profile and assign users, or turn field security off if it does not need protecting.
Where a change to sensitive data, or a failure in the code that touches it, leaves no trace.
Auditing a secured column needs the org, table, and column switches all on. If any is off, no history is written, so who changed a sensitive value and when cannot be reconstructed.
Fix. Turn on all three switches and set retention to match your compliance requirement.
A plugin catch block that neither logs nor rethrows makes failures vanish with no trace. Read deterministically from the compiled IL; severity rises when the component writes a sensitive field.
Fix. Rethrow or trace the error before continuing; if swallowing is intended, say so in a trace line.
Credentials left in the open, and outbound calls or entry points that move data or accept traffic without protection.
A credential sits in a plugin step's unsecure config, readable by anyone who can read the registration and carried into solution exports and source control. Pathix reports the pattern and a redacted length, never the value.
Fix. Move it to Azure Key Vault via an environment variable, and rotate the exposed credential.
A service endpoint or webhook uses http, so record data, execution context, and any credential in the message cross the network unencrypted.
Fix. Re-register against https, verify the certificate, and rotate any credential sent in the clear.
A webhook with no key or header cannot prove its calls are genuine, so anyone who learns the URL can post traffic that looks authentic.
Fix. Add a header-based secret and require it at the receiver; rotate it periodically.
The flow's trigger accepts anyone with the URL, no identity check, so a leaked link lets anyone drive whatever the flow does in Dataverse. Power Automate now defaults new flows to tenant-restricted.
Fix. Restrict the trigger to your tenant or named users, and regenerate the signature key so old URLs stop working.
Broken, orphaned, looping, or wasteful automation, and surfaces built on retired platform pieces. Genuinely valuable, and often the reason a migration bid goes sideways, but they are not compliance-control findings. We keep them clearly separate so the security findings above keep their weight.
Broken, orphaned, looping, or wasteful automation. Real environment-health value, but not evidence for a security control, and we label it that way on purpose.
The step re-queues itself on every save, doubling side effects and burning depth budget. Only the platform's depth guard keeps it from an infinite loop.
Fix. Move the write to a PreOperation Target modification, or drop the column from the step's filter.
Two steps on the same table, message, and stage share an execution order and write overlapping columns, so the final value is nondeterministic and can flip after a redeploy or import.
Fix. Give the steps distinct execution orders, placing the one that should win last.
The same plugin runs twice on every matching operation, doubling side effects and outbound calls for no benefit. A classic re-import artifact.
Fix. Unregister the duplicate, keeping the one with the correct configuration.
No filtering attributes means the step runs on every update of every column, which autosave multiplies, spending compute and feeding re-trigger loops. Microsoft's guidance calls for filters on every Update step.
Fix. Register filtering attributes for only the columns the step reacts to.
These are the hottest messages on the platform, so every list view, lookup, and API read pays the step's cost. Worst when it makes an outbound call on every read.
Fix. Move the logic off the read path; if it must stay, keep it fast with no outbound calls or extra queries.
An Update trigger with no selected columns runs on every column change, which autosave multiplies, spending run capacity and feeding re-trigger loops. The flow-plane version of an unfiltered Update step.
Fix. Set the trigger's Select columns to only the columns the flow reacts to.
A synchronous step routes to a webhook inside the user's save, so a slow or unreachable endpoint delays or fails every affected save. The same integration run asynchronously carries none of this risk.
Fix. Re-register the step asynchronously unless the caller truly needs the response before the save commits.
Flows cannot run through an unbound reference, so enabled ones fail at run time and disabled ones fail to activate. Common after an import skipped the connection wiring.
Fix. Bind the reference to a service-account connection and test each flow.
The platform stopped the flow, usually a data-loss-prevention policy violation, so its business process silently stopped while the flow still appears configured. The DLP cause is a real governance signal.
Fix. Read the suspension reason, fix the connector or policy conflict, and resubmit for activation.
Successful async runs pile up in the system-job table without bound, slowing the async queue and consuming storage.
Fix. Turn on delete-on-success so completed jobs purge while failures stay for troubleshooting.
A step is registered against a table Pathix could not resolve in the scan, so its writes and reads do not appear in dependency results. Often a deleted table with a stale registration.
Fix. Confirm the target exists and is in scope; unregister the step if the table was retired.
A flow action targets a table Pathix could not match, so its writes and reads are missing from dependency results. Often an excluded entity or a mistyped name.
Fix. Confirm the action targets the table you expect; fix the target or bring the table into scope.
A connection reference exists but no flow binds to it. Not an active risk alone; the privileges of the underlying connection are the concern if it is ever re-bound.
Fix. Delete it if genuinely unused, and rotate any broad or secret-bearing connection underneath.
An endpoint is registered but nothing triggers it. Not an active risk alone, but an over-permissioned or long-unused endpoint is worth cleaning up.
Fix. Delete it if unused and rotate any credential baked into its definition.
A component works in Dataverse but the parser could not extract its full structure, so its writes and reads are incomplete in results. We treat these as parser bugs, not environment problems, and surface them rather than hide the gap.
Fix. Report it with the component name; fixes land in the parser. Retire the component if it is unused.
Surfaces built on connectors and controls the platform has retired or moved on from.
Flows on the retired Dynamics 365 connector no longer run; flows on the deprecated legacy Dataverse connector work on borrowed time with no fixes.
Fix. Recreate the connection on the current Dataverse connector and rebind each flow.
A form declares a deprecated control (the older Flip Switch, slider, or knob controls) that gets no fixes or accessibility improvements and can break after a platform update.
Fix. Swap it for the modern equivalent (Toggle, number input, supported slider) and re-test the form.
Every scan runs these checks against your real Dynamics customization graph, ranked by severity, each with the principals it exposes and the control it answers to.