多租户ASP.NET应用中用户上传Razor视图的隔离实现方案问询
Great question—this is a critical balance between customization and security in multi-tenant ASP.NET apps. Let’s break down how to tackle both your concerns clearly.
The core goal here is to ensure each tenant’s custom views can’t be accessed or executed by another tenant. Here are actionable strategies:
Physical Directory Isolation
Store each tenant’s uploaded views in a dedicated, tenant-specific directory structure, likewwwroot/TenantCustomViews/{TenantId}/. Never mix views from different tenants in the same folder. Configure your app to only load views from the directory matching the current request’sTenantId—you can do this with a customIViewLocationExpanderthat dynamically injects the tenant ID into view lookup paths.Custom View Engine with Tenant Validation
Extend the defaultRazorViewEngineto add a security check before loading any view. Override theFindViewmethod to verify that the resolved view path falls within the current tenant’s directory. If it doesn’t, return a "view not found" result to block cross-tenant access.Restrict Dynamic Compilation (or Sandbox It)
Razor views are compiled into .NET assemblies by default, which is risky because user-provided C# code could execute arbitrary logic. Instead:- Use a library like RazorLight that supports runtime rendering with sandboxing options.
- If you must compile, use Roslyn’s compilation APIs with strict restrictions: block access to dangerous namespaces like
System.IO,System.Data, orSystem.Reflection, and limit the types and methods that can be called.
In a database-multi-tenant app, you need to lock down both file system and database access for user-uploaded views:
Sandbox Code Execution
Never let user-provided Razor code run in the same context as your main app. Use a dedicated sandbox environment for rendering custom views:- Restrict the code’s permissions using .NET’s
CodeAccessPermission(for .NET Framework) or isolate the rendering process in a separate, low-privilege process (better for .NET Core/.NET 5+). - Use a templating library that explicitly blocks C# code execution (like Liquid or Handlebars) if full Razor flexibility isn’t strictly necessary—this eliminates most security risks.
- Restrict the code’s permissions using .NET’s
Isolate View Contexts
When rendering a custom view, only pass a tenant-scoped view model to it. Never expose the global database context, file system utilities, or any cross-tenant services directly to the view. For example, create aTenantSpecificViewModelthat contains only data the current tenant is allowed to access, and enforce that views can only interact with this model.Enforce File System Permissions
- Run your app pool (if using IIS) with a low-privilege identity that only has read/write access to the current tenant’s directory.
- Wrap all file operations in a tenant-aware service (e.g.,
ITenantFileService) that validates every file path against the current tenant’s allowed directory—never let views callFile.ReadAllTextor similar methods directly.
Leverage Database Tenant Filters
Since you already have database multi-tenancy configured, ensure your EF Core (or ORM) uses global query filters (viaHasQueryFilter) to automatically filter out non-tenant data. Even if a view somehow gets access to a database context, it will only retrieve data belonging to the current tenant. Just make sure you never expose the raw context to the view itself—always go through a service layer that enforces tenant isolation.
A critical warning: Allowing users to upload custom Razor views introduces significant security risks, even with these safeguards. If possible, prioritize safer customization options (like no-code template editors or static file uploads) over letting users execute arbitrary C# code.
内容的提问来源于stack exchange,提问作者barrett777

