Restrictions (Deny rules) are a second, inverted permission layer, and a well-meaning role change can silently take access away through them. If your analysis only rolls up grants, you will not see it coming.

On one production system, adding a single shared add-on role removed customer create and edit from a group of unrelated users. None of those users lost a grant. The add-on carried a Deny that belonged to a different role, and in D365 a Deny beats any Grant and applies across the user, so the moment the add-on landed, the Deny switched off access those users had held for years through entirely separate roles.

How a Deny differs from a Grant

Standard security thinking is additive. A user's effective access is the union of every privilege every assigned role grants. Add a role, access can only grow. That mental model is what makes the failure invisible.

Restrictions invert it. A restriction is an explicit Deny on a securable, and D365 resolves conflicts in one direction only: Deny wins. It does not matter how many roles grant the thing, how senior the user is, or which legal entity they are in. One Deny anywhere in the user's assigned roles removes the access for that user, everywhere.

So a role is not only a bag of grants. It can also be a bag of Denies. When you hand someone a role for the grants it carries, you hand them its Denies too.

Why normal analysis cannot see it

Role roll-up logic answers the question "what can this user do." It sums grants. Restrictions answer the opposite question, "what is this user forbidden to do," and they are a different column, a different table, and a different resolution rule. A tool that models access as a union of grants will report that the add-on only adds, because in grant terms it does. The removal happens in a layer the model never consulted.

This is why a change can pass every "no access lost" check built on grant comparison and still strip access in production. The check was looking at the wrong layer.

The failure was systemic, not a one-off

Once the production case surfaced, a full review found restrictions were mishandled at every stage of the redesign chain, not just in that one role:

Every one of these is invisible to grant-based analysis. Each one either removes access a user should keep or grants access a user should not have.

The fix is a gate that halts on drift

The durable fix was not a smarter diff. It was a mandatory gate in the deployment pipeline that compares the full restriction set before and after, at the level the restrictions actually resolve (privilege, restriction verb, and direct table or field verb), and stops the pipeline cold on any restriction gained or lost. Export and revocation both depend on the gate passing, and the local packaging tool runs the same check against what it actually ships, not against the design.

The engine also stopped putting restrictions into shared add-ons at all, and asserts that invariant, so a Deny can never again ride into a non-holder on a role handed out for its grants.

On the client that triggered the investigation, the gate now balances with zero access gained and zero unexplained losses: in the latest build, 21,625 held keys went in, 20,776 were delivered, and the remaining 849 were recorded, signed-off waivers for users with no business role in the new design, with zero non-holder grants and nothing dropped silently. Every key is accounted for; nothing disappears silently. Honoring restrictions properly also changed the shape of the package. When the correction first landed it grew the design by over a dozen roles, because nearly twice as many roles turned out to be restriction carriers as the old design had counted; the package has since been restructured and carries 11 dedicated restriction-carrier roles. That is not bloat. It is the access the old design was quietly going to break.

Bottom line

Any security change, including one sold as a cost reduction, can revoke production access through restrictions, and grant-based analysis will never warn you. Treat restriction drift as a release blocker, not a report. If your change process cannot answer "did any user gain or lose a single Deny," it is not safe to ship, no matter how clean the grant diff looks.

Frequently asked questions

Is this unique to a redesign or optimization project?

No. Any change that alters a user's set of assigned roles can change their effective Denies. Adding a role, removing a role, merging two roles, all of it. Optimization work just does it at scale, which is why it surfaces the problem.

We never author custom Denies. Are we safe?

Check before you assume. Restrictions can sit inside custom duties, directly on roles as table or field permissions, and inside ISV content. "We do not write Denies" is a claim to verify against the actual configuration, not a given.

What should we ask a vendor delivering a security change?

Ask whether their process compares the restriction layer, not just grants, and whether a restriction change blocks the release. If the answer is about grant roll-ups only, the Deny layer is unguarded.