Skip to content

Remoting Policies — item-based CLM, command allowlists, and approved scripts for SPE remoting #1426

@michaellwest

Description

@michaellwest

Remoting Policies -- item-based CLM, command allowlists, and approved scripts for SPE remoting

Summary

SPE remoting runs all remote scripts in FullLanguage mode with no granular control over what commands or modules a remote caller can use. This issue tracks the implementation of item-based Remoting Policies that bring defense-in-depth to SPE's remoting surface.

Guiding principles:

  • Opt-in, not opt-out -- no predetermined default policies ship with SPE. Admins create policies as needed.
  • Items-only -- all policies are Sitecore content items. No XML config profiles.
  • Allowlist-only -- only "Allowed Commands" exist. No blocklist/allowlist mode toggle.
  • Secure by default -- ConstrainedLanguage is the default; Full Language is an opt-in checkbox.
  • Fail-closed -- API Keys without a policy deny all requests. Deleted policies deny all requests.

Remoting Policy Template

Path: /sitecore/templates/Modules/PowerShell Console/Remoting/Remoting Policy

Section: General

Field Type Default Description
Full Language Checkbox Unchecked Controls the PowerShell language mode. When unchecked, scripts run in ConstrainedLanguage mode.
Audit Level Droplist Violations Controls how much detail is logged per policy. See Audit Level Details below.

Section: Inline Script Restrictions

Field Type Description
Allowed Commands Multi-Line Text Permitted commands for inline scripts, one per line. Does not apply to Web API scripts invoked by reference.

Section: Script Approval

Field Type Description
Approved Scripts Multilist Web API scripts permitted for execution by reference. Uses a script data source that queries all Web API integration points across modules.

Enforcement model

Endpoint Gate What happens
Inline script Allowed Commands Each command is checked against the allowlist. Unlisted commands are rejected.
v2 by reference Approved Scripts The script must be in the policy's Approved Scripts list. Unlisted scripts are denied.

Both endpoints respect the Full Language checkbox for language mode.

Audit Level Details

Audit logging uses a two-pass model. Events that occur before a policy is resolved (authentication, API key validation, hard security rejections) are always logged unconditionally regardless of the policy's Audit Level setting. Events that occur after policy resolution are gated by the policy's Audit Level.

Always logged (unconditional, pre-policy or security-critical)

These events always appear in the audit log:

Action Description
requestReceived Incoming remoting request (entry point)
authenticated Successful authentication
bearerAuthSuccess JWT bearer auth succeeded
bearerAuthFailed JWT bearer auth failed
bearerAuthError JWT bearer auth error
apiKeyValidated API key matched
authRejected Authentication rejected
userUnauthorized User not authorized for service
pathTraversalBlocked Path traversal attempt blocked
apiKeyDenied API key has no policy assigned

Violations (default) -- security violations and data transfers

Includes everything unconditional, plus:

Action Description
commandBlocked Command blocked by policy allowlist
scriptRejected Script blocked by service-level allowlist
scriptRejectedByPolicy Script blocked by policy allowlist
scriptNotApproved Script not in approved scripts list
throttled Request throttled
fileUploaded File uploaded to server
fileDownloaded File downloaded from server
mediaUploaded Media item uploaded
mediaUpdated Media item updated
mediaDownloaded Media item downloaded

File and media transfer events are included at this level because data exfiltration is a security-relevant event.

Standard -- operational audit trail

Includes everything in Violations, plus:

Action Description
throttleBypassed Throttle limit exceeded but request allowed (bypass mode)
scriptStarting Script execution starting
scriptApproved Script approved by policy
policyAudit Policy evaluation summary
scriptCompleted Script execution completed
scriptFailed Script execution failed with error
sessionCleanup Server-side session disposed
connectionTest Connection test endpoint invoked

Full -- verbose diagnostics

Includes everything in Standard, plus additional detail on each request:

Action Description
scriptDetail Script body length and language mode at execution start
responseDetail Output format and HTTP status code at completion
requestDetail Query parameter count (values are not logged)

Note: Full is intended for troubleshooting and development environments. It should not be enabled in production long-term due to log volume.

Design notes

  • The Unrestricted sentinel policy (used for legacy shared-secret sessions without an API Key) defaults to Standard to ensure the most privileged access path has visibility.
  • The Denied sentinel policy (used when a referenced policy is invalid) defaults to Violations.
  • If the Audit Level field value cannot be parsed, the system defaults to Violations and logs a warning.

Remoting API Keys

API keys are managed as Sitecore content items. Each key binds a shared secret to a remoting policy.

Path: /sitecore/templates/Modules/PowerShell Console/Remoting/Remoting API Key

Field Type Description
Enabled Checkbox Activate/deactivate this key.
Shared Secret Single-Line Text The authentication secret for this key. Minimum 32 characters for HS256, 48 for HS384, 64 for HS512 (per RFC 7518). Use a random hex string, e.g. 64 characters.
Policy Droplink Required. The remoting policy that governs this key.
Impersonate User Single-Line Text Required. The Sitecore identity for the remote session. The JWT Name claim is ignored - this field is the authoritative identity.
Request Limit Integer Maximum requests allowed within the throttle window.
Throttle Window Integer Time window in seconds for rate limiting.
Throttle Action Droplist Action when the throttle limit is exceeded. Block (default) rejects with HTTP 429. Bypass allows the request through and logs the event.

Consumer workflow

The standard SPE remoting module handles API Key authentication automatically. No manual JWT construction needed. The server matches the shared secret to the API Key, reads the bound policy, and enforces it.

Key behaviors

  • Policy is mandatory -- an API Key with no policy assigned returns 403 on all requests.
  • Impersonate User is mandatory -- an API Key without an Impersonate User returns 403. The JWT Name claim is not trusted for identity when an API key is matched; the Impersonate User field is the authoritative identity for the session.
  • Per-key throttling -- rate limiting is enforced per API Key with X-RateLimit response headers.
  • Throttle action -- Block rejects with 429; Bypass allows through with audit log (monitoring mode).
  • Timing-safe comparison -- all secret comparisons use SecureCompare.FixedTimeEquals.
  • Minimum secret length -- enforced per RFC 7518 Section 3.2: 32 characters for HS256, 48 for HS384, 64 for HS512. Secrets shorter than the HMAC hash output reduce the effective key space. A warning is logged at API key load time and requests are rejected at validation time if the secret is too short.

Security Model

Authentication flow

  1. Bearer token validated (JWT signature checked against HS256 allowlist)
  2. API Key items checked first; legacy config shared secret as fallback
  3. Identity established (user from JWT Name claim or API Key impersonation)

Authorization flow

  1. If API Key is present but has no policy: 403 (denied)
  2. If no API Key: Unrestricted (backward compatible for legacy shared-secret sessions)
  3. Policy resolved from API Key's Droplink field, then endpoint-specific enforcement applies

Fail-closed defaults

  • API Keys with no policy: denied
  • Deleted or unparseable policy items: denied
  • The system defaults to no access in every ambiguous state

Response headers

Header Description
X-SPE-LanguageMode Active language mode for the session
X-SPE-Restriction Set on 403 responses (command-blocked or policy-blocked)
X-SPE-BlockedCommand The specific command that was blocked
X-SPE-Policy The remoting policy that blocked the command
X-RateLimit-Limit Request limit for the throttle window
X-RateLimit-Remaining Requests remaining in the current window
X-RateLimit-Reset Unix timestamp when the throttle window resets
Retry-After Seconds to wait before retrying (only on 429)

Test Coverage

Integration tests (Remoting.RemotingPolicies.*)

  1. Policy language mode enforcement (via API Key)
  2. Command allowlist enforcement (inline endpoint)
  3. Execution escape prevention (dynamic invocation blocking)
  4. Backward compatibility (legacy shared secret = unrestricted)
  5. Policy enforcement after exceptions
  6. Dynamic invocation rejection
  7. Publish-Item allowed by policy
  8. API Key without policy is denied
  9. Script approval (v2 endpoint -- approved vs unapproved scripts)

Integration tests (Remoting.Throttle.*)

  1. Block action -- requests within limit succeed
  2. Block action -- exceeding limit returns 429 with rate-limit headers
  3. Bypass action -- requests within limit succeed
  4. Bypass action -- exceeding limit returns 200 with audit log
  5. Default action (empty field) -- behaves like Block

Remaining Work

Documentation (tracked in Book repo)

  • Migration guide for existing installations
  • Remoting policies documentation
  • API keys documentation
  • Approved script management documentation
  • Update web-services.md with remoting policy support
  • Update remoting.md with bearer auth, API keys, and error handling
  • Update security overview with CLM layer
  • CLM scripting guide (allowed patterns, New-PSObject usage)

Known Issues

  • SOAP endpoint (RemoteAutomation.asmx) does not enforce remoting policies (disabled by default)
  • Find-Item -Criteria hashtable coercion under CLM needs investigation
  • Script hash in audit logs is truncated (16 hex chars) -- consider full SHA-256 or a script registry
  • v1 endpoint is deprecated but still active -- consider adding a deprecation warning or kill switch

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions