-
Notifications
You must be signed in to change notification settings - Fork 162
Description
Context
SharePoint's new baseline security policy "Don't allow new custom scripts in OneDrive and SharePoint sites" enforces DenyAddAndCustomizePages across all modern sites as a tenant-wide default. Unlike the previous per-site AddAndCustomizePages setting — which admins could temporarily disable during or prior to provisioning — the new baseline policy cannot be toggled off at the site level, making DenyAddAndCustomizePages a permanent reality for most tenants.
Microsoft introduced the tenant-level override AllowWebPropertyBagUpdateWhenDenyAddAndCustomizePagesIsEnabled (via Set-SPOTenant) specifically so that property bag operations can continue to work under this policy. SharePoint also continues to allow SPFx custom actions (identified by a non-empty ClientSideComponentId) on NoScript sites.
PnP.Framework does not account for either of these exceptions.
Problem
1. Property bag operations silently skipped
IsNoScriptSite() checks only EffectiveBasePermissions.Has(AddAndCustomizePages). When it returns true, all property bag handlers short-circuit client-side without ever calling the API — even when the tenant explicitly allows property bag writes via AllowWebPropertyBagUpdateWhenDenyAddAndCustomizePagesIsEnabled.
AllowWebPropertyBagUpdateWhenDenyAddAndCustomizePagesIsEnabled appears zero times in the codebase.
Affected operations:
ObjectPropertyBagEntry.ProvisionObjects(line 34) — all web property bag entries from provisioning templatesObjectPersistTemplateInfo.ProvisionObjects(line 28) —_PnP_ProvisioningTemplateId/_PnP_ProvisioningTemplateInfoWebExtensions.ReIndexWeb(line 1005) —vti_searchversionwrite, falls back to per-list reindexBrandingExtensions.SetThemeByUrl(lines 284, 316) —InheritThemeproperty bag write
Additionally, the WillProvision methods in ObjectPropertyBagEntry (line 190) and ObjectPersistTemplateInfo (line 63) use the same IsNoScriptSite() check. Since the provisioning pipeline calls WillProvision before ProvisionObjects, the handlers are never even invoked.
2. SPFx custom action updates blanket-blocked on NoScript sites
The add path for site/web custom actions has a partial SPFx exemption (checks ClientSideComponentId), but:
- Update path (
ObjectCustomActions.UpdateCustomAction, line 165): blanketif (isNoScriptSite) return;— blocks all updates including SPFx - List-level add/update (
ObjectListInstance, lines 1554 and 2022): entire custom action blocks wrapped inif (!isNoScriptSite)— no per-action SPFx check
SPFx extensions that need their ClientSideComponentProperties updated on an existing site are silently skipped.
Proposed Solution
Property Bag: Probe-and-Delegate
Introduce a new IsPropertyBagWriteBlocked(Web) method and use it in place of the IsNoScriptSite() guard at all property bag call sites. The method has two modes:
- Caller provides the value → the method uses it directly, no API calls at all.
- Caller provides nothing (
null) → the method falls back to a sentinel write probe on NoScript sites to determine whether the tenant override is active. No tenant-admin permissions required.
For the provisioning pipeline, a PropertyBagWriteAllowed property on ProvisioningTemplateApplyingInformation is populated once at pipeline start and consumed by both WillProvision and ProvisionObjects (keeping them in sync). This property is public so callers who already know the tenant flag value can provide it directly, skipping the probe entirely. When left null (the default), the probe runs automatically — zero breaking changes.
Extension methods (ReIndexWeb, SetThemeByUrl) that operate outside the provisioning pipeline gain an optional bool? propertyBagWriteAllowed = null parameter with the same semantics.
Why this design:
-
No universal detection path exists. There is no site-level API to read
AllowWebPropertyBagUpdateWhenDenyAddAndCustomizePagesIsEnabled. Admin CSOM requires the tenant admin URL and admin permissions — not available to all callers. The sentinel probe is the only universal fallback, but costs 2–3 CSOM round-trips. -
Caller delegation eliminates the probe entirely. Callers who already know the tenant setting (from a prior admin API call, PnP PowerShell, or configuration) can provide the value directly. When provided, no probe is executed — the supplied value is used as-is. The sentinel probe only runs as a fallback when no value is provided (
null). -
PnP PowerShell's approach is too invasive for a library. PnP PowerShell (PR #5176) catches
ServerUnauthorizedAccessException, clones to the admin URL, reads the tenant flag, and temporarily disables NoScript to force writes through. This modifies site security settings as a side effect — inappropriate for a general-purpose library. -
Backward compatible. All new parameters default to
null. Existing callers pass nothing →null→ the probe runs as before. Zero breaking changes.
Custom Actions: Per-Action SPFx Exemption
Currently, the add path for site/web custom actions already checks ClientSideComponentId to let SPFx actions through on NoScript sites. However, the update path and all list-level custom action paths use a blanket NoScript block that skips everything — including SPFx actions.
The fix introduces a small helper IsSPFxCustomAction(CustomAction) that returns true when ClientSideComponentId != Guid.Empty. Each of the blanket-blocked paths (UpdateCustomAction, ObjectListInstance.UpdateCustomActions, ObjectListInstance.CreateList) is changed from "skip all actions if NoScript" to "skip only non-SPFx actions if NoScript". SPFx extensions pass through regardless of NoScript status, matching what SharePoint itself allows.
IsNoScriptSite() remains unchanged — all non-property-bag guards (master pages, navigation, publishing, etc.) continue using it as-is.