Script sandbox: System.AppContext (SetSwitch/SetData/GetData/BaseDirectory) is reachable from scripts #792

Closed
opened 2026-11-10 07:11:46 -05:00 by dohertj2 · 1 comment
Owner

Found while building the lmxopcua#780 P2 shadow-mode tests.

What. System.AppContext is not in ForbiddenTypeAnalyzer.ForbiddenFullTypeNames (which does list System.Environment, System.AppDomain, System.GC, System.Activator). A script can therefore call:

  • System.AppContext.BaseDirectory — the host's install path (minor information disclosure; the shadow-mode tests use exactly this to tell the worker from the host: System.AppContext.BaseDirectory.Contains("script-worker") compiles and runs);
  • System.AppContext.GetData(name) — e.g. TRUSTED_PLATFORM_ASSEMBLIES, APP_CONTEXT_DEPS_FILES;
  • System.AppContext.SetSwitch(name, bool) / SetData(name, object) — process-wide runtime switches. In process (the default Scripting:Runner=InProcess) a Designer-authored script can flip switches that change the host's own behaviour (serialization / networking / globalization compatibility switches read lazily by the BCL).

Why it matters. Same class as System.Environment / System.AppDomain, which are already denied for process-wide control. Script authoring is gated by ConfigEditor (Administrator or Designer), so this is p3. Out of process (#780) the blast radius is the worker only.

Suggested fix. Add System.AppContext to ForbiddenFullTypeNames (or deny SetSwitch/SetData/GetData as forbidden members and keep BaseDirectory if there is a legitimate use — there is not one I can see), plus a ScriptSandboxHardeningTests case per member. Note the #780 shadow tests (ShadowModeTests, InWorker constant) rely on AppContext.BaseDirectory as a test-only divergence; they would need another way to tell the worker apart (e.g. a test-only input) when this lands.

Found while building the lmxopcua#780 P2 shadow-mode tests. **What.** `System.AppContext` is not in `ForbiddenTypeAnalyzer.ForbiddenFullTypeNames` (which does list `System.Environment`, `System.AppDomain`, `System.GC`, `System.Activator`). A script can therefore call: - `System.AppContext.BaseDirectory` — the host's install path (minor information disclosure; the shadow-mode tests use exactly this to tell the worker from the host: `System.AppContext.BaseDirectory.Contains("script-worker")` compiles and runs); - `System.AppContext.GetData(name)` — e.g. `TRUSTED_PLATFORM_ASSEMBLIES`, `APP_CONTEXT_DEPS_FILES`; - **`System.AppContext.SetSwitch(name, bool)` / `SetData(name, object)`** — process-wide runtime switches. In process (the default `Scripting:Runner=InProcess`) a Designer-authored script can flip switches that change the host's own behaviour (serialization / networking / globalization compatibility switches read lazily by the BCL). **Why it matters.** Same class as `System.Environment` / `System.AppDomain`, which are already denied for process-wide control. Script authoring is gated by `ConfigEditor` (Administrator or Designer), so this is p3. Out of process (#780) the blast radius is the worker only. **Suggested fix.** Add `System.AppContext` to `ForbiddenFullTypeNames` (or deny `SetSwitch`/`SetData`/`GetData` as forbidden members and keep `BaseDirectory` if there is a legitimate use — there is not one I can see), plus a `ScriptSandboxHardeningTests` case per member. Note the #780 shadow tests (`ShadowModeTests`, `InWorker` constant) rely on `AppContext.BaseDirectory` as a test-only divergence; they would need another way to tell the worker apart (e.g. a test-only input) when this lands.
dohertj2 added the type:review-findingpriority:p3 labels 2026-11-10 07:11:46 -05:00
Author
Owner

Fixed in 0e96dd08 (master).

Change. ForbiddenTypeAnalyzer (shared by the in-process compile and the ScriptWorker compile) now denies, as types: System.AppContext (whole type incl. BaseDirectory/GetData/TryGetSwitch), System.Console, System.Runtime.GCSettings, System.Runtime.ProfileOptimization, System.Text.EncodingProvider/CodePagesEncodingProvider, System.ComponentModel.TypeDescriptor, System.Security.Cryptography.CryptoConfig (AddAlgorithm/AddOID mutate a process-wide map, CreateFromName is an Activator equivalent). As members: CultureInfo.DefaultThreadCurrentCulture/DefaultThreadCurrentUICulture/CurrentCulture/CurrentUICulture and TimeZoneInfo.ClearCachedData. The member deny-list previously only matched invoked methods; it now also matches properties and plain member accesses.

Audit (already denied, now covered by tests): AppDomain.SetData, Environment.SetEnvironmentVariable/Exit/FailFast, GC.*, Thread.CurrentThread.CurrentCulture, ThreadPool.SetMinThreads (System.Threading prefix). Not blocked, noted: Delegate.DynamicInvoke/CreateDelegate, System.Linq.Expressions Compile, WindowsIdentity.RunImpersonated (thread-scoped, no process state change).

Shadow tests. Divergence is now a culture-sensitive string.Compare("a","B") (worker is InvariantGlobalization => ordinal; host linguistic), with a premise test against the real worker. No product-side seam.

Evidence. New Rejects_process_wide_mutators (24 cases) + diagnostic-name tests + a legit-usage control (InvariantCulture, GetCultureInfo, TimeZoneInfo.Utc compile fine). Mutation: with the analyzer reverted, 21 of the new tests fail; restored => green. Release build 0 warnings; AKKA_TEST_TIMEFACTOR=3: Core.Scripting 283/1 skip, ScriptWorker 161/4 skip, ScriptedAlarms 109, VirtualTags 16, Calculation 84, Host 275, Runtime 1002/3 skip, OpcUaServer.IntegrationTests 133, all green. Repo seeds/fixtures/docs examples grep clean for the newly blocked APIs.

Before upgrading DEV/QA: check the ConfigDbs' script sources (VirtualTag, Calculation, ScriptedAlarm scripts) for AppContext, Console., CultureInfo.Current*/DefaultThread*, GCSettings, CryptoConfig, TypeDescriptor, EncodingProvider, TimeZoneInfo.ClearCachedData — any script using them will now fail to compile at load.

Fixed in 0e96dd08 (master). **Change.** `ForbiddenTypeAnalyzer` (shared by the in-process compile and the ScriptWorker compile) now denies, as types: `System.AppContext` (whole type incl. `BaseDirectory`/`GetData`/`TryGetSwitch`), `System.Console`, `System.Runtime.GCSettings`, `System.Runtime.ProfileOptimization`, `System.Text.EncodingProvider`/`CodePagesEncodingProvider`, `System.ComponentModel.TypeDescriptor`, `System.Security.Cryptography.CryptoConfig` (AddAlgorithm/AddOID mutate a process-wide map, CreateFromName is an Activator equivalent). As members: `CultureInfo.DefaultThreadCurrentCulture/DefaultThreadCurrentUICulture/CurrentCulture/CurrentUICulture` and `TimeZoneInfo.ClearCachedData`. The member deny-list previously only matched invoked methods; it now also matches properties and plain member accesses. **Audit (already denied, now covered by tests):** `AppDomain.SetData`, `Environment.SetEnvironmentVariable/Exit/FailFast`, `GC.*`, `Thread.CurrentThread.CurrentCulture`, `ThreadPool.SetMinThreads` (System.Threading prefix). Not blocked, noted: `Delegate.DynamicInvoke/CreateDelegate`, `System.Linq.Expressions` Compile, `WindowsIdentity.RunImpersonated` (thread-scoped, no process state change). **Shadow tests.** Divergence is now a culture-sensitive `string.Compare("a","B")` (worker is `InvariantGlobalization` => ordinal; host linguistic), with a premise test against the real worker. No product-side seam. **Evidence.** New `Rejects_process_wide_mutators` (24 cases) + diagnostic-name tests + a legit-usage control (InvariantCulture, GetCultureInfo, TimeZoneInfo.Utc compile fine). Mutation: with the analyzer reverted, 21 of the new tests fail; restored => green. Release build 0 warnings; AKKA_TEST_TIMEFACTOR=3: Core.Scripting 283/1 skip, ScriptWorker 161/4 skip, ScriptedAlarms 109, VirtualTags 16, Calculation 84, Host 275, Runtime 1002/3 skip, OpcUaServer.IntegrationTests 133, all green. Repo seeds/fixtures/docs examples grep clean for the newly blocked APIs. **Before upgrading DEV/QA:** check the ConfigDbs' script sources (VirtualTag, Calculation, ScriptedAlarm scripts) for `AppContext`, `Console.`, `CultureInfo.Current*`/`DefaultThread*`, `GCSettings`, `CryptoConfig`, `TypeDescriptor`, `EncodingProvider`, `TimeZoneInfo.ClearCachedData` — any script using them will now fail to compile at load.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#792