* refactor(auth): remove the vestigial per-author script permission plumbing #7975 is closed won't-do: authoring a workflow is a trusted act, and a per-author gate would not change what a script can do once it runs. The host switch stays the control, and it is per language, so an untrusted author gets a host with the switch off rather than a permission. That settles what the code was still half-carrying. WorkflowDefinitionScriptAuthorizationService took a ClaimsPrincipal it never read, and could return a MissingPermission reason nothing produced; two call sites branched on that reason to send a 403 that could not happen. The expression-descriptor endpoint kept a map from expression type to per-author permission whose values went unused even before the permissions were retired -- it only ever tested membership, and the decision was always IsBrowsable. Each of these reads as an authorization gate to anyone scanning the file, and none of them is one. The principal, the unreachable reason, and both dead branches are gone. The map becomes a set of the expression types the host can switch off, which is what it was actually being used as. Behaviour is unchanged: the only failure is a language the host disabled, which is a property of the deployment and so a 400 naming the switch, never a 403. PermissionNames loses ExecuteCSharpExpressions and ExecutePythonExpressions, which existed only for that map and the test mirroring it. Five other legacy constants there are also unreferenced but belong to other modules; they are left alone rather than swept up here. Two tests asserting the host-and-user case were exact duplicates of the host-only case once the principal stopped mattering, so they go with it. The migration guide said deployments lose per-author granularity "until #7975 lands" and advised disabling host code until then. That promise is withdrawn and replaced with the actual guidance. Closes #7975 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(wiki): drop the retired exec:* permissions from the scripting guide Review found doc/wiki/expressions-and-scripting.md still telling operators that API callers "must have the exec:csharp-expressions permission" to author, publish, dispatch or execute workflows containing C#, and the same for Python. Those permissions no longer exist, so the instruction cannot be followed and describes a gate that is not there. Both sections now say what is actually true: the host switch is the whole control, there is no per-caller permission because a workflow runs under the server's authority rather than the caller's, and an untrusted author gets a host with the switch off. The switches are noted as independent, since enabling Python while leaving C# off is a real posture. My earlier sweep searched for the issue number rather than the permission strings, which is why this file was missed. Refs #7975 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
6.7 KiB
Expressions And Scripting
Expressions let workflow inputs be dynamic. The base expression feature provides evaluator infrastructure; language modules add concrete evaluators, descriptors, activities, and type/function definitions.
Base Expressions
ExpressionsFeature registers:
IExpressionEvaluatorIWellKnownTypeRegistry
The base project is Elsa.Expressions. It is intentionally small and does not own language-specific runtime behavior.
Language Modules
| Module | Feature | Evaluator | Notes |
|---|---|---|---|
| Elsa.Expressions.JavaScript | JavaScriptFeature | Jint-backed IJavaScriptEvaluator |
Adds type definitions, function definitions, RunJavaScript, and FastEndpoints assembly. |
| Elsa.Expressions.CSharp | CSharpFeature | Roslyn scripting-backed ICSharpEvaluator |
Adds RunCSharp, descriptors, and C# options. |
| Elsa.Expressions.Python | PythonFeature | pythonnet-backed IPythonEvaluator |
Registers PythonGlobalInterpreterManager as a hosted service. |
| Elsa.Expressions.Liquid | LiquidFeature | Fluid-backed Liquid manager | Adds Liquid filters and parser services. |
JavaScript
JavaScript is the richest expression module. It registers:
IJavaScriptEvaluatorITypeDefinitionService- type describers and type definition renderers
- function definition providers
- variable definition providers
RunJavaScriptactivity- TypeScript definition support
- expression descriptors for Studio
Configuration example from Elsa.Server.Web/Program.cs:
elsa.UseJavaScript(options =>
{
options.AllowClrAccess = true;
options.ConfigureEngine(engine =>
{
engine.Execute("function greet(name) { return `Hello ${name}!`; }");
});
});
Additional JavaScript libraries are in Elsa.Expressions.JavaScript.Libraries, including Lodash, Lodash FP, and Moment feature packages.
CSharp
CSharpFeature registers C# descriptors and ICSharpEvaluator, then adds activities from its assembly. The reference server demonstrates configuring wrappers and appending helper scripts:
elsa.UseCSharp(options =>
{
options.AllowHostCodeExecution = true;
options.DisableWrappers = disableVariableWrappers;
options.AppendScript("string Greet(string name) => $\"Hello {name}!\";");
});
Roslyn C# scripting is privileged host-code execution, not a sandbox. Hosts must explicitly set CSharpOptions.AllowHostCodeExecution to true before C# expressions or RunCSharp can be authored or executed. That switch is the whole control: there is no per-caller permission, because a workflow runs under the server's authority rather than the caller's, so gating the caller never constrained what a script could do. Any author who may write workflow definitions may use C# where the switch is on. If only some of your authors are trusted with host code, give the others a host with the switch off — see #7975.
Python
PythonFeature registers pythonnet-based evaluation and configures PythonGlobalInterpreterManager as a hosted service. Python.NET execution is privileged host-code execution, not a sandbox. Python code can access host process capabilities through pythonnet and must only be enabled for trusted workflow authors.
Hosts must explicitly set PythonOptions.AllowHostCodeExecution to true before Python expressions or RunPython can be authored or executed. As with C#, that switch is the whole control and there is no per-caller permission; the switches are independent, so Python can be enabled while C# stays off. Hosts must also configure the Python DLL path or set PYTHONNET_PYDLL.
The reference server binds Scripting:Python configuration in Program.cs.
Python.NET hardening is part of the broader script execution security tracking in #7096.
Liquid
LiquidFeature registers Fluid options, parser services, expression descriptors, and built-in filters:
- array filters
- string filters
- number filters
- miscellaneous filters
base64keys
The reference server configures the Fluid encoder to HtmlEncoder.Default.
Expression Descriptors
Expression descriptors let Studio know which expression languages are available and how to present them. Providers are registered by language features, for example:
JavaScriptExpressionDescriptorProviderCSharpExpressionDescriptorProviderPythonExpressionDescriptorProviderLiquidExpressionDescriptorProvider
The API exposes descriptors under /elsa/api/descriptors/expression-descriptors.
Type Aliases
Expression modules and activity modules register type aliases through ExpressionOptions. HTTP, for example, adds aliases such as HttpRequest, HttpResponse, RouteData, FormFile, and Downloadable in HttpFeature.
ElsaScript Relationship
ElsaScript does not replace expression languages. It uses Elsa's expression providers through language prefixes such as js =>, cs =>, py =>, and liquid =>. See ElsaScript README.
Testing
Expression tests are split by concern:
- test/unit/Elsa.Expressions.UnitTests
- test/integration/Elsa.JavaScript.IntegrationTests
- test/integration/Elsa.Dsl.ElsaScript.IntegrationTests
- workflow integration tests under test/integration/Elsa.Workflows.IntegrationTests/Evaluation
Prefer unit tests for parser/evaluator behavior and integration tests when expression evaluation interacts with workflow variables, activity outputs, or designer descriptors.