diff --git a/docs/en/framework/infrastructure/text-templating/razor.md b/docs/en/framework/infrastructure/text-templating/razor.md index d2f690c72c..917006c7bd 100644 --- a/docs/en/framework/infrastructure/text-templating/razor.md +++ b/docs/en/framework/infrastructure/text-templating/razor.md @@ -10,6 +10,14 @@ The Razor template is a standard C# class, so you can freely use the functions of C#, such as `dependency injection`, using `LINQ`, custom methods, and even using `Repository`. +> ⚠ **Security notice** +> +> The Razor rendering engine compiles template content into a fully-trusted .NET assembly via Roslyn and executes it in the host process. **Editing a Razor template at runtime is functionally equivalent to executing arbitrary server-side code** — the template can access the filesystem, environment variables, the application's DI container, secrets, and any other .NET API. +> +> The framework reflects this fact through the `ITemplateRenderingEngine.IsSandboxed` property: `RazorTemplateRenderingEngine.IsSandboxed == false`. The [Text Template Management](../../../modules/text-template-management.md) module reads this flag and requires the dedicated `TextTemplateManagement.TextTemplates.EditNonSandboxedContents` permission (in addition to `EditContents`) before allowing such templates to be edited via its UI. +> +> Treat the ability to edit Razor template content as equivalent to granting shell access to the application server, and grant the related permission only to fully trusted developers/operators. If you need a sandboxed engine for content editors, consider [Scriban](scriban.md), whose templates cannot invoke arbitrary .NET APIs. + ## Installation diff --git a/docs/en/modules/text-template-management.md b/docs/en/modules/text-template-management.md index fa62ee7470..32b7cc31a9 100644 --- a/docs/en/modules/text-template-management.md +++ b/docs/en/modules/text-template-management.md @@ -164,6 +164,22 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do See the `TextTemplateManagementPermissions` class members for all permissions defined for this module. +The module exposes two edit-time permissions with different risk levels: + +| Permission | Required to edit | Default grant | +|------------|------------------|---------------| +| `TextTemplateManagement.TextTemplates.EditContents` | Templates rendered by a sandboxed engine (e.g. Scriban). Editing such templates is safe for content editors because the engine cannot execute arbitrary .NET code. | Granted to roles that need to edit template text. | +| `TextTemplateManagement.TextTemplates.EditNonSandboxedContents` | Templates rendered by a **non-sandboxed** engine (e.g. Razor). Editing such templates is functionally equivalent to granting server-side code execution because the engine compiles the content into a .NET assembly that runs with the host process's privileges. | **Not granted to any role by default**, including `admin`. Must be granted explicitly. | + +Whether a template is sandboxed is determined by `ITemplateRenderingEngine.IsSandboxed` on the engine that renders it. Editing a non-sandboxed template requires **both** `EditContents` and `EditNonSandboxedContents`. + +The Text Template Management UI surfaces this distinction: + +- A warning banner is rendered above the editor for non-sandboxed templates. +- The save and restore buttons are disabled when the current user lacks `EditNonSandboxedContents` for a non-sandboxed template. + +> Treat `EditNonSandboxedContents` as equivalent to granting shell access to the application server. Only assign it to fully trusted developers or operators. + ### Angular UI diff --git a/docs/en/release-info/migration-guides/abp-10-4.md b/docs/en/release-info/migration-guides/abp-10-4.md index 213d8b7230..9087f3b493 100644 --- a/docs/en/release-info/migration-guides/abp-10-4.md +++ b/docs/en/release-info/migration-guides/abp-10-4.md @@ -141,6 +141,80 @@ Configure(options => > See [#25235](https://github.com/abpframework/abp/pull/25235) for details. +### Text Template Rendering Engine — `IsSandboxed` Marker + +**Who is affected** + +- Custom rendering engines that implement `Volo.Abp.TextTemplating.ITemplateRenderingEngine` directly (not deriving from `TemplateRenderingEngineBase`). +- Modules and applications that surface template editing to non-developer users (e.g. the Text Template Management module). + +**What changed** + +- `ITemplateRenderingEngine` exposes a new required property: + + ```csharp + bool IsSandboxed { get; } + ``` + + Sandboxed engines (e.g. Scriban) interpret templates as a restricted DSL without .NET interop. Non-sandboxed engines (e.g. Razor) compile templates into fully-trusted .NET code that runs with the same privileges as the host process. +- `TemplateRenderingEngineBase` provides a virtual default of `false` (secure-by-default): engines that derive from the base class and don't override the property are treated as non-sandboxed. +- `RazorTemplateRenderingEngine` declares `IsSandboxed => false` (compiles to .NET assembly via Roslyn). +- `ScribanTemplateRenderingEngine` declares `IsSandboxed => true` (DSL with no .NET interop). + +**What to do** + +- If your application registers a custom engine by implementing `ITemplateRenderingEngine` directly (without deriving from `TemplateRenderingEngineBase`), add the property: + + ```csharp + public bool IsSandboxed => false; // or true if your engine cannot execute host code + ``` + +- If your engine derives from `TemplateRenderingEngineBase`, no action is required for compilation; however, override `IsSandboxed => true` if your engine is genuinely sandboxed so callers (such as the Text Template Management module) treat its templates as safe to edit by non-developer users. + +> See [#XXXXX](https://github.com/abpframework/abp/pull/XXXXX) for details. + +### Text Template Management — `EditContents` Permission Split (security) + +**Who is affected** + +- Applications using the Text Template Management module that have granted the `TextTemplateManagement.TextTemplates.EditContents` permission to roles that are not fully trusted server administrators/developers. +- In particular, applications that use Razor templates (default for solutions referencing `Volo.Abp.TextTemplating.Razor`) and have any non-developer role with `EditContents`. + +**What changed** + +- A new permission `TextTemplateManagement.TextTemplates.EditNonSandboxedContents` has been added. +- Editing a template whose rendering engine is **non-sandboxed** (i.e. `ITemplateRenderingEngine.IsSandboxed == false`, e.g. Razor) now requires **both** permissions: `EditContents` and `EditNonSandboxedContents`. +- The new permission is **not granted to any role by default**, including the `admin` role. The previously implicit assumption — that `EditContents` was enough to edit Razor templates — has been corrected: editing such templates is functionally equivalent to granting server-side code execution and is now gated by an explicit permission whose name communicates that risk. +- The Text Template Management UI (MVC, Blazor) renders a security warning banner when the current template's engine is non-sandboxed, and disables the save/restore buttons when the current user lacks the new permission. + +**What to do** + +After upgrading, audit which roles currently hold `EditContents` and decide which of them should also be granted `EditNonSandboxedContents`: + +1. Roles that should only edit sandboxed templates (e.g. Scriban, Liquid, plain HTML) need no further action — they keep editing those templates. +2. Roles that need to continue editing Razor templates must be granted `EditNonSandboxedContents` explicitly via the Permission Management page. +3. If — and only if — you have reviewed your role assignments and confirmed that every role currently holding `EditContents` is trusted to execute server-side code through Razor templates, you may seed the new permission for those roles via your `IDataSeedContributor`: + + ```csharp + var grants = await _permissionGrantRepository.GetListAsync( + TextTemplateManagementPermissions.TextTemplates.EditContents); + + foreach (var grant in grants) + { + await _permissionManager.SetAsync( + TextTemplateManagementPermissions.TextTemplates.EditNonSandboxedContents, + grant.ProviderName, + grant.ProviderKey, + isGranted: true); + } + ``` + +> ⚠ Do **not** automate this seeding for arbitrary tenants/roles without first reviewing the current grants — auto-restoring the previously-implicit elevated trust would defeat the security improvement. The recommended path is to grant the new permission only to specific developer/operator roles via the Permission Management UI. + +If your application's data seeder grants all permissions in the `TextTemplateManagement` group to a role (e.g. the `admin` role), that role will automatically receive `EditNonSandboxedContents` on first run after upgrade. Audit your seeder if you want stricter defaults. + +> See the [Razor Integration](../../framework/infrastructure/text-templating/razor.md) document and [#XXXXX](https://github.com/abpframework/abp/pull/XXXXX) for details. + ### Dependency Updates **Who is affected** diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateRenderingEngine.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateRenderingEngine.cs index e100825011..51e57027b6 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateRenderingEngine.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateRenderingEngine.cs @@ -9,6 +9,26 @@ public interface ITemplateRenderingEngine { string Name { get; } + /// + /// Indicates whether this engine renders template content in a sandboxed way that + /// prevents the content from accessing the host runtime (filesystem, environment, + /// arbitrary .NET APIs, etc.). + /// + /// Sandboxed engines (e.g. Scriban, Liquid) interpret templates as a restricted DSL + /// without .NET interop. Non-sandboxed engines (e.g. Razor) compile templates into + /// fully-trusted .NET code that runs with the same privileges as the host process. + /// + /// + /// Implementations are required to declare this explicitly. The recommended + /// secure-by-default value is false: any engine that doesn't have a clear + /// sandboxing story should return false so callers such as the + /// TextTemplateManagement module treat its templates as requiring elevated trust + /// to edit. provides a virtual default + /// of false for engines deriving from it. + /// + /// + bool IsSandboxed { get; } + /// /// Renders a text template. /// diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs index 62c8dee43c..1c01854f33 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs @@ -8,6 +8,9 @@ public abstract class TemplateRenderingEngineBase : ITemplateRenderingEngine { public abstract string Name { get; } + /// + public virtual bool IsSandboxed => false; + protected readonly ITemplateDefinitionManager TemplateDefinitionManager; protected readonly ITemplateContentProvider TemplateContentProvider; protected readonly IStringLocalizerFactory StringLocalizerFactory; diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs index 485ca44121..58d1b5df96 100644 --- a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs +++ b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs @@ -17,6 +17,15 @@ public class RazorTemplateRenderingEngine : TemplateRenderingEngineBase, ITransi public const string EngineName = "Razor"; public override string Name => EngineName; + /// + /// + /// Razor templates are compiled into .NET assemblies via Roslyn and executed in the + /// host process with full access to the BCL and DI container. They are NOT sandboxed, + /// and editing template contents is functionally equivalent to granting server-side + /// code execution. + /// + public override bool IsSandboxed => false; + protected readonly IServiceScopeFactory ServiceScopeFactory; public RazorTemplateRenderingEngine( diff --git a/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs index 7a5ac79592..58a70ea806 100644 --- a/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs +++ b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs @@ -14,6 +14,15 @@ public class ScribanTemplateRenderingEngine : TemplateRenderingEngineBase, ITran public const string EngineName = "Scriban"; public override string Name => EngineName; + /// + /// + /// Scriban interprets templates as a restricted DSL without direct .NET interop. + /// Templates cannot invoke arbitrary BCL types unless explicitly imported into the + /// script object by the host, so editing template content is safe for non-developer + /// users. + /// + public override bool IsSandboxed => true; + public ScribanTemplateRenderingEngine( ITemplateDefinitionManager templateDefinitionManager, ITemplateContentProvider templateContentProvider, diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine_IsSandboxed_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine_IsSandboxed_Tests.cs new file mode 100644 index 0000000000..aba81af6f3 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine_IsSandboxed_Tests.cs @@ -0,0 +1,29 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating.Razor; + +public class RazorTemplateRenderingEngine_IsSandboxed_Tests : AbpTextTemplatingTestBase +{ + private readonly RazorTemplateRenderingEngine _engine; + + public RazorTemplateRenderingEngine_IsSandboxed_Tests() + { + _engine = GetRequiredService(); + } + + [Fact] + public void Razor_Engine_Should_Not_Be_Sandboxed() + { + // Razor templates compile into fully-trusted .NET code; editing them is + // equivalent to granting server-side code execution. + _engine.IsSandboxed.ShouldBeFalse(); + } + + [Fact] + public void Razor_Engine_Should_Expose_IsSandboxed_Through_Interface() + { + ITemplateRenderingEngine asInterface = _engine; + asInterface.IsSandboxed.ShouldBeFalse(); + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine_IsSandboxed_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine_IsSandboxed_Tests.cs new file mode 100644 index 0000000000..a82f7cc038 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine_IsSandboxed_Tests.cs @@ -0,0 +1,29 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating.Scriban; + +public class ScribanTemplateRenderingEngine_IsSandboxed_Tests : AbpTextTemplatingTestBase +{ + private readonly ScribanTemplateRenderingEngine _engine; + + public ScribanTemplateRenderingEngine_IsSandboxed_Tests() + { + _engine = GetRequiredService(); + } + + [Fact] + public void Scriban_Engine_Should_Be_Sandboxed() + { + // Scriban interprets templates as a restricted DSL without .NET interop; + // editing template content is safe for non-developer users. + _engine.IsSandboxed.ShouldBeTrue(); + } + + [Fact] + public void Scriban_Engine_Should_Expose_IsSandboxed_Through_Interface() + { + ITemplateRenderingEngine asInterface = _engine; + asInterface.IsSandboxed.ShouldBeTrue(); + } +}