-
-
Notifications
You must be signed in to change notification settings - Fork 71
Description
Summary
Predefined Constrained Language Mode (CLM) profiles and configuration for securing SPE remoting endpoints. Based on an audit of all 291 serialized PowerShell scripts across the release/9.0 branch, this proposal catalogs every .NET API, Sitecore library, function, and cmdlet in use, then defines tiered restriction profiles, a script trust model, and a hybrid config/item-based management system.
Scope: Remoting endpoints only (/api/spe/remoting, RESTful v2, custom web API services). ISE, Console, context menus, ribbons, pipelines, event handlers, and scheduled tasks remain unchanged at FullLanguage.
1. API & Cmdlet Catalog
Dangerous Patterns Identified
| Pattern | Location | Risk |
|---|---|---|
| Reflection with nonpublic BindingFlags | Remoting.yml, Remoting2.yml | Bypasses access control |
[scriptblock]::Create() |
Rules Based Report.yml | Dynamic code execution |
Add-Type -AssemblyName |
Expand-Archive.yml | Assembly loading |
| Direct SQL via SqlClient | Invoke-SqlCommand.yml | Database access |
[System.Web.Security.Membership] |
Enforce password expiration.yml | Identity manipulation |
Not found (good): Invoke-Expression, Start-Process, Add-Type -TypeDefinition, Add-Type -Path
Full catalog of .NET APIs, Sitecore APIs, SPE cmdlets, and standard PowerShell cmdlets available in the design document.
2. Restriction Profiles
Four tiered profiles combining PowerShell's native LanguageMode with SPE's commandRestrictions. All profiles are opt-in -- fully backward-compatible.
Profile: unrestricted (Default)
- LanguageMode: FullLanguage
- Command Restrictions: None
- Use case: Trusted admin scripts, development environments
Profile: read-only-sitecore
- LanguageMode: ConstrainedLanguage
- Blocked:
Set-Item,Remove-Item,New-Item,Move-Item,Copy-Item,Rename-Item,Publish-Item,Protect-Item,Unprotect-Item,Install-Package,New-Package,Set-Layout,Add-Rendering,Remove-Rendering - Allowed .NET Types: Primitives only (
[int],[string],[bool],[DateTime],[guid],[regex]) - Use case: Reporting integrations, dashboards, monitoring
Profile: read-only-full
- Extends: read-only-sitecore`
- Additional blocks:
Invoke-SqlCommand,Set-Content,Out-File,Export-Csv,Export-Clixml,Add-Type,Compress-Archive,Expand-Archive,Send-SheerMessage - Blocked .NET Types: All non-primitive types (via CLM)
- Blocked Patterns:
[System.IO.File]::Write*,[System.Data.SqlClient.*] - Use case: Untrusted external consumers, third-party integrations
Profile: content-editor
- LanguageMode: ConstrainedLanguage
- Mode: Allowlist
- Allowed: All read cmdlets from
read-only-sitecore+Set-Item(content fields only),Publish-Item,New-Item(content items only),Lock-Item,Unlock-Item - Standard fields (
__prefix) blocked by default -- only content fields (defined on the item's own template) are writable. Admins can allowlist specific standard fields via config. - Use case: Content management APIs, headless CMS integrations
Profile Inheritance
Single-level only (e.g., read-only-full extends read-only-sitecore). Resolved via load-time flattening -- the ProfileManager merges parent + child blocklists at config load into a flat RestrictionProfile object. No runtime chain-walking.
Add-Type Handling
Block Add-Type entirely in constrained profiles with configurable assembly allowlist:
- Allowed:
System.IO.Compression,System.IO.Compression.FileSystem - Always blocked:
-TypeDefinition(compiles C#),-Path(loads DLLs),-MemberDefinition(P/Invoke)
SQL Access
Block Invoke-SqlCommand entirely in constrained profiles. If SQL read access is needed, a future Invoke-SqlQuery cmdlet wrapping ExecuteReader only is safer than parsing SQL strings.
File System
read-only-sitecore: Allow file reads, block file writesread-only-full: Block all direct file system .NET calls; allow onlyGet-Content,Test-Path,Resolve-Path,Get-ChildItem
3. Trusted Scripts Model
Problem
SPE scripts use Import-Function, Invoke-Script, and Execute-Script to load Script Library items. These often require .NET type access that CLM blocks. A blanket CLM enforcement would break most SPE built-in functionality.
Solution: GUID + Content Hash Trust Registry
Trust operates on script items (not individual function names) since a single script item (e.g., DialogBuilder) can define multiple functions.
<trustedScripts>
<script name="Remoting"
itemId="{E5F6A7B8-...}"
contentHash="sha256:..."
trust="System"
allowTopLevel="true">
<exports>
<function>ConvertTo-CliXml</function>
<function>ConvertFrom-CliXml</function>
</exports>
</script>
<script name="DialogBuilder"
itemId="{B3E2F1A2-...}"
contentHash="sha256:..."
trust="Trusted">
<exports>
<function>New-DialogBuilder</function>
<function>Add-DialogField</function>
<function>Show-Dialog</function>
</exports>
</script>
</trustedScripts>Trust Levels
- Untrusted (default) -- runs under caller's language mode and command restrictions
- Trusted -- can use .NET types and CLM bypass; no reflection access
- System -- reflection + private member access allowed; config-only assignment
Why GUIDs
- Sitecore item GUIDs are assigned at creation and never change (survive renames, moves, serialization)
- SPE ships Script Library items via serialization with fixed GUIDs
- Cannot create a new item with the same GUID -- Sitecore enforces uniqueness
- Prevents shadowing: a user-created script with the same name but different GUID will not get trust elevation
Content Hash Integrity
SHA256 of the script body stored alongside the trust entry. Catches tampering or accidental modification.
<script name="..."
itemId="{...}"
contentHash="sha256:abc123..."
trust="Trusted"
onHashMismatch="constrain" /> <!-- or "block" or "warn" -->Execution Entry Points
Trust applies at the common execution layer, not per-cmdlet:
| Method | Trust Check | Export Manifest |
|---|---|---|
Import-Function |
GUID + hash | Yes -- controls which functions get elevated status |
Invoke-Script |
GUID + hash | No -- return values subject to caller's type restrictions |
Execute-Script (internal) |
GUID + hash (remoting only) | Context-dependent |
Export Manifest (Import-Function only)
When a trusted script is loaded via Import-Function:
- Script body executes in FullLanguage (defines functions)
- Functions actually defined are compared against declared
<exports> - Undeclared functions are either removed (strict) or forced constrained (permissive)
- Prevents a compromised script from injecting elevated functions that shadow built-ins
Resolution Order
In constrained sessions, Import-Function always prefers the GUID-registered script over a name match, preventing shadowing.
Auto-Generation
A build task parses serialized YAML files to auto-generate <trustedScripts> with correct GUIDs, content hashes, and export lists (via AST parsing of function definitions).
4. Scope Claim Integration
JWT scope claims map to profile names:
scope=read-only-sitecoreapplies that profilescope=content-editorapplies that profile- No scope or unknown scope falls back to service default
- Multiple scopes: most restrictive wins (intersection)
Builds on the existing scopeRestrictions system in ScriptValidator.cs.
5. Audit Logging
Configurable per profile:
| Level | What's Logged |
|---|---|
None |
No logging |
Violations |
Blocked commands, denied .NET types, failed trust checks |
Standard |
Violations + script execution start/end with user context |
Full |
Standard + every command executed with arguments |
Pipeline scripts (LoggedIn, LoggingIn, Logout) always run in FullLanguage and are logged with [INFO] Pipeline script executing in FullLanguage (exempt from profile restrictions).
6. Hybrid Configuration Model
Base Configuration (Spe.config)
Profiles defined as XML patches. Ship with SPE, provide secure defaults. All services default to unrestricted.
Item-Based Overrides (Phase 2)
Admins extend/override via Sitecore items under /sitecore/system/Modules/PowerShell/Settings/Restriction Profiles/. Merge behavior: most restrictive wins.
7. Migration & Backward Compatibility
All new functionality is opt-in. Existing installations see no behavior change.
Phased Rollout
- Phase 1:
RestrictionProfileclass, config loading with load-time flattening, service-to-profile mapping, command blocklist/allowlist enforcement, GUID-based script trust registry, violations-level audit logging, standard field blocking forcontent-editor, JWT scope-to-profile mapping - Phase 2: Item-based overrides, Restriction Profile Override template, enhanced trust management UI
- Phase 3: AST-based function classification (ReadOnly/WriteCapable/Elevated), content hash auto-generation in build
- Phase 4: Documentation and migration guide
Adoption
One attribute change per service:
<remoting enabled="true" requireSecureConnection="true" profile="read-only-sitecore" />8. New Components
| Component | Purpose |
|---|---|
RestrictionProfile |
Data model: name + language mode + command restrictions + trust config + audit level |
ProfileManager |
Config loading, inheritance flattening, caching |
TrustedScriptEntry |
GUID + content hash + trust level + export manifest |
ScriptTrustRegistry |
Trust lookup by item GUID at execution time |
Extended ScriptValidator |
Enforce profile restrictions during script execution |
Extended WebServiceSettings |
Map services to profiles |
Performance
- Profile resolution cached per service+scope combination
- AST analysis cached per script revision
- Trust lookups by GUID (O(1) dictionary)
- All caches respect existing
Spe.AuthorizationCacheExpirationSecsandSpe.WebApiCacheExpirationSecs
Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Profile inheritance depth | Single level only | Avoids merge-order ambiguity |
| Inheritance resolution | Load-time flattening | Simpler, faster than runtime chain-walking |
| Trust identity | Item GUID + content hash | GUIDs are immutable and unique; hash catches tampering |
| SQL in constrained profiles | Block entirely | Safer than parsing SQL strings; future Invoke-SqlQuery cmdlet for read-only access |
| Temporary remoting elevation | Not supported | Use separate endpoints with different profiles instead |
| AST analysis timing | Sync on first request, cached | Pre-analysis on save misses scripts modified outside Sitecore |
| Composable scopes | Intersection only | Union would defeat the purpose of restrictions |
| Standard fields in content-editor | Blocked by default (__ prefix) |
Prevents security/workflow/rendering tampering; allowlist for exceptions |