Skip to content

NoScript detection blocks property bag operations that SharePoint would actually allow #1224

@nicolaor

Description

@nicolaor

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 templates
  • ObjectPersistTemplateInfo.ProvisionObjects (line 28) — _PnP_ProvisioningTemplateId / _PnP_ProvisioningTemplateInfo
  • WebExtensions.ReIndexWeb (line 1005) — vti_searchversion write, falls back to per-list reindex
  • BrandingExtensions.SetThemeByUrl (lines 284, 316) — InheritTheme property 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): blanket if (isNoScriptSite) return; — blocks all updates including SPFx
  • List-level add/update (ObjectListInstance, lines 1554 and 2022): entire custom action blocks wrapped in if (!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:

  1. 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.

  2. 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).

  3. 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.

  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions