Find a file
Sipke Schoorstra 85e5fc083c
fix(external-authentication): scope role-deletion impact to the role's tenant (#8036)
* fix(external-authentication): scope role-deletion impact to the role's tenant

ExternalAuthenticationRoleDeletionDependencyContributor scanned every stored
connection with an empty ConnectionFilter and every configured connection
regardless of its tenant, so a role ID that exists in two tenants could report
another tenant's references as its own impact -- and a configuration entry
owned by another tenant could block a role deletion outright. Remediation had
the same reach: it loaded a dependency's connection by the caller-supplied
owner ID without checking which tenant owned it.

Impact, prevalidation and remediation now only see connections in the role's
tenant context, which is the tenant active on ITenantAccessor while the
role-deletion coordinator runs. Host-scoped connections stay in scope for every
tenant, because the connection registry resolves the host scope for every
signing-in tenant and the provisioner resolves a connection's default role IDs
in the signing-in user's tenant, so a host connection naming a role ID really
does reference that tenant's role. Configuration entries that leave the tenant
blank are host-scoped for the same reason the configuration source materializes
them there. A connection carrying another tenant's ID is out of scope in both
directions, and a connection loaded for remediation that is not in the role's
tenant is treated as absent, which fails the request rather than mutating it.

The stored connections are fetched per applicable scope so another tenant's
rows are never materialized, and both connection stores already honor
ConnectionFilter.Scope; the durable store now has a test pinning that, since
the tenant boundary rests on it.

Refs #8013

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): scan every tenant when deleting a tenant-agnostic role

Role stores expose tenant-agnostic roles (TenantId == "*") from every tenant, but the
role-deletion contributor derived its dependency scan boundary from the ambient tenant only,
so deleting an agnostic role while tenant A was active left references from other tenants
dangling. Resolve the role being deleted once per operation, through the active role store,
and scan every connection and configuration entry regardless of tenant when it is agnostic.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(external-authentication): share the active role store lookup

Extract the duplicated "active role store is the last registration"
resolution into a single ActiveRoleStore accessor and rename ToScope to
ToConnectionScope for clarity.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): read one connection snapshot and prefer the agnostic role

Reading the host and tenant scopes as two separate store queries let a connection
whose TenantId changed mid-flight fall between the reads and escape both, letting
role deletion proceed while a reference remained. FindConnectionsInRoleTenantScopeAsync
now reads one snapshot and filters it in memory. IsAgnosticRoleAsync resolved a role
by an unqualified ID lookup, which could return the ambient tenant's role instead of
an agnostic role sharing its ID, silently narrowing impact scanning and leaving
JIT-policy references in other tenants dangling; it now checks every role sharing the
ID and gives the agnostic scope deterministic precedence.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(external-authentication): correct the scope-filter test comment

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): fail closed when a role ID resolves to more than one role

A same-ID collision between a tenant-scoped role and an agnostic role can only
occur in MemoryRoleStore (durable persistence keys roles by ID alone). In that
case the coordinator's own deletion target is already ambiguous, so widening
or narrowing the scope by guessing is wrong in either direction; throw instead
of picking a side.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): scope role-deletion impact by the resolved role's tenant

Replace the isAgnosticRole flag with ResolveRoleTenantIdAsync, which returns the
resolved role's own TenantId and falls back to the ambient tenant only when the
role cannot be resolved. With multitenancy disabled the EF role store installs
no tenant query filter and can resolve a tenant-owned role by ID regardless of
the ambient tenant, so scoping by the ambient tenant alone left that role's
connection references out of scan while the coordinator deleted it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): require an agnostic replacement when remediating an agnostic role

Authorization for a replacement role still resolves through the ambient tenant's
role services, so a deletion initiated in tenant A could authorize a tenant-A-only
replacement and then write it into tenant B's connection policy, where that role
does not exist. When the deletion target is agnostic, require the replacement
role to be agnostic too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): require agnostic replacements for host connections and reject ambiguous ones

Extend the agnostic-replacement requirement to host-scoped connections, since a host
connection is served to every signing-in tenant and a tenant-scoped replacement would
resolve in the authorizing tenant but fail to resolve in every other tenant it serves.
Recheck the replacement at removal time through the same agnostic-role resolution used
at validation, instead of trusting whichever same-ID role a plain FindAsync happens to
return, so a replacement collision introduced between validation and mutation is
rejected. Resolve IsAgnosticRoleAsync's candidate directly and return true only when
exactly one matching role is agnostic, so an ambiguous replacement ID is reported as
replacement_role_unavailable_or_unauthorized instead of escaping as an exception.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): keep host-connection replacements allowed for default-tenant roles

Revert the host-scope replacement guard added for host-scoped connections.
IdentityProviderConnectionManagementService forces every managed connection
to host scope, and in a deployment without multitenancy roles are created
scoped to the default tenant rather than agnostic, so requiring an agnostic
replacement for host-scoped connections would make every replacement
remediation impossible in the default deployment. The replacement guard
applies only when the deletion target itself is agnostic, as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 22:34:44 -07:00
.agents/skills release: anchor recovery workflow validation (#8025) 2026-09-05 10:30:06 -07:00
.claude/skills [codex] Add live server log streaming diagnostics (#7438) 2026-05-11 00:08:52 +02:00
.config Fix Race Condition Causing Duplicate Trigger Registration in Multi-Engine Environments + Add Concurrency Tests to Prevent Regression (#7131) 2025-12-05 09:17:14 +01:00
.github Merge remote-tracking branch 'origin/release/3.8.0' 2026-09-06 00:06:14 +02:00
.nuke Upgrade to NUKE 9.0.4 and fix automatic workflow generation 2025-01-26 19:29:19 +02:00
.specify feat(user-tasks): add identity-neutral workflow-bound human tasks (#7955) 2026-08-25 00:09:06 +02:00
.vscode docs: ratify constitution v1.0.0 (7 principles from codebase analysis) 2026-03-08 15:49:20 +01:00
announcements Skills 2026-06-25 15:19:31 +02:00
build build: bump NuGet.Packaging to 7.9.0 to unbreak the NUKE build on SDK 10.0.400 2026-08-11 23:25:33 +02:00
design fix(deps): clear the 12 open npm advisories in both lockfiles (#7988) 2026-08-25 06:19:16 +02:00
doc refactor(identity)!: retire the SecurityRoot policy in favour of endpoint permissions (#8003) 2026-08-28 22:28:29 +02:00
docker [codex] Fix console log metadata and type resolution (#7542) 2026-05-30 22:52:01 +02:00
gen Update DSL and sample 2022-03-28 13:25:45 +02:00
scripts test(user-tasks): add a persistence conformance suite with fault injection (#7986) 2026-08-25 04:36:18 +02:00
specs Merge remote-tracking branch 'origin/release/3.8.0' 2026-09-06 00:06:14 +02:00
src fix(external-authentication): scope role-deletion impact to the role's tenant (#8036) 2026-09-06 22:34:44 -07:00
test fix(external-authentication): scope role-deletion impact to the role's tenant (#8036) 2026-09-06 22:34:44 -07:00
.editorconfig Refactor to use target-typed object creation 2025-01-09 10:27:55 +01:00
.gitignore chore: anchor the Release/ ignore pattern to the repository root 2026-08-22 23:06:17 +02:00
AGENTS.md test(user-tasks): add a persistence conformance suite with fault injection (#7986) 2026-08-25 04:36:18 +02:00
build.cmd Upgrade to NUKE 8.1 (#5952) 2024-09-12 09:56:06 -07:00
build.ps1 Upgrade to NUKE 8.1 (#5952) 2024-09-12 09:56:06 -07:00
build.sh Upgrade to NUKE 8.1 (#5952) 2024-09-12 09:56:06 -07:00
CLAUDE.md docs: add agent operating principles to AGENTS.md and CLAUDE.md 2026-05-06 12:44:27 +02:00
CONTEXT.md feat(user-tasks): add identity-neutral workflow-bound human tasks (#7955) 2026-08-25 00:09:06 +02:00
CONTRIBUTING.md test(user-tasks): add a persistence conformance suite with fault injection (#7986) 2026-08-25 04:36:18 +02:00
Directory.Build.props Addresses warnings and enforces null safety (#7050) 2025-11-12 15:53:23 +01:00
Directory.Build.targets fix(build): make ConfigureAwait.Fody weaving actually take effect (#7983) 2026-08-25 01:37:23 +02:00
Directory.Packages.props Merge remote-tracking branch 'origin/release/3.8.0' 2026-09-06 00:06:14 +02:00
dotnet-install.sh Add ActivityCompleted notification for workflow activity completion (#6675) 2025-05-24 23:01:41 +02:00
Elsa.sln fix(external-auth)!: wildcard-aware permission grant boundary, and startup smoke tests for both hosts (#7985) 2026-08-25 05:25:29 +02:00
Elsa.sln.DotSettings feat: extend shells integration and modular server support (#7399) 2026-04-18 14:33:34 +02:00
icon.png Debug 2022-12-20 12:47:41 +01:00
jordi-delay-bookmark-reply.md Skills 2026-06-25 15:19:31 +02:00
LICENSE Add sources from experimental repo 2022-01-04 09:42:12 +01:00
NuGet.Config Fix scheduling startup backlog catch-up 2026-08-27 23:54:42 +02:00
pau-delay-dispatch-response.md Skills 2026-06-25 15:19:31 +02:00
README.md [codex] Fail fast on default JWT signing keys (#7496) 2026-05-20 22:30:49 +02:00
ROADMAP.md Include website and documentation updates in Elsa releases (#8021) 2026-09-05 06:47:06 -07:00
run-dsl-tests.sh Add Elsa Script DSL (#7076) 2025-11-25 19:57:50 +01:00
test-flowchart.txt Add Elsa Script DSL (#7076) 2025-11-25 19:57:50 +01:00
test-simple-flowchart.elsa Add Elsa Script DSL (#7076) 2025-11-25 19:57:50 +01:00

Elsa Workflows

Elsa Workflows

Elsa 3 Prerelease Nuget (with prereleases) Feedz Docker Image Version (latest semver) Discord Stack Overflow questions Gurubase DeepWiki

For Elsa 2, Click Here

Introduction

Elsa is a powerful workflow library that enables workflow execution within any .NET application. Elsa allows you to define workflows in various ways, including:

  • Writing C# code
  • Using a visual designer
  • Specifying workflows in JSON

Elsa ships with a powerful visual designer

Try with Docker

To give the Elsa Studio + Elsa Server a quick spin, you can run the following command to start the Elsa Docker container:

docker pull elsaworkflows/elsa-server-and-studio-v3:latest
docker run -t -i -e ASPNETCORE_ENVIRONMENT='Development' -e HTTP_PORTS=8080 -e HTTP__BASEURL=http://localhost:13000 -p 13000:8080 elsaworkflows/elsa-server-and-studio-v3:latest

This Docker image is based on a reference ASP.NET application that hosts both the workflow server and designer and is not intended for production use.

For any non-development deployment, inject a secure random JWT signing key through environment variables or a secrets manager instead of using committed appsettings values. For code-first hosts such as Elsa.Server.Web, set Identity__Tokens__SigningKey. For shell-based hosts, set the shell feature key, for example CShells__Shells__Default__Features__Identity__SigningKey.

By default, you can access http://localhost:13000 and log in with:

  Username: admin
  Password: password

TLS and custom certificate authorities

All Elsa Docker images now ship with the operating system's certificate authority bundle baked in at build time. This means you can call public HTTPS endpoints such as https://example.com without any additional configuration.

If you need to trust a private or corporate CA, mount the certificate bundle into the container and reference it via EXTRA_CA_CERT:

docker run \
  -v /path/to/company-ca.crt:/certs/company-ca.crt:ro \
  -e EXTRA_CA_CERT=/certs/company-ca.crt \
  elsaworkflows/elsa-server-and-studio-v3:latest

On startup, the container copies the certificate into /usr/local/share/ca-certificates and runs update-ca-certificates, making the trust available to .NET, OpenSSL, curl, and other system components. Multiple certificates can be provided by pointing EXTRA_CA_CERT at a directory containing .crt or .pem files.

In highly restricted environments where you cannot modify the system trust store, you can instead rely on the standard SSL_CERT_FILE or SSL_CERT_DIR environment variables:

docker run \
  -v /path/to/company-ca-bundle.pem:/certs/custom.pem:ro \
  -e SSL_CERT_FILE=/certs/custom.pem \
  elsaworkflows/elsa-server-and-studio-v3:latest

Installing the CA bundle adds roughly 300KB to the Debian-based images. No package managers run at container startup; all trust updates happen immutably at build time or via the mounted certificates shown above.

Table of Contents

Documentation

Elsa Documentation Website.

Known Issues and Limitations

Elsa is continually evolving, and while it offers powerful capabilities, there are some known limitations and ongoing work:

  • Documentation is still a work in progress.
  • Input/Output is not yet implemented in the Workflow Instance Viewer.
  • Starting workflows from the designer is currently supported only for workflows that do not require input and do not start with a trigger; this is planned for a future release.
  • The designer currently only supports Flowchart activities. Support for Sequence and StateMachine activities is planned for a future release.
  • UI input validation is not yet implemented.

Features

Elsa offers a wide range of features for building and executing workflows, including:

  • Execution of workflows in any .NET application with support for .NET 6 and beyond.
  • Support for both short-running and long-running workflows.
  • A programming model loosely inspired by Windows Workflow Foundation.
  • A web-based drag & drop designer with support for custom activities.
  • Native support for activity composition, including activities like Sequence, Flowchart, and ForEach.
  • Parallel execution of activities.
  • Built-in activities for common scenarios, such as sending emails, making HTTP calls, scheduling tasks, sending and receiving messages, and more.
  • Workflow versioning and migration via API.
  • Easy integration with external applications via HTTP, message queues, and more.
  • Actor model for increased workflow throughput.
  • Dynamic expressions with support for C#, JavaScript, Python, and Liquid.
  • Persistence agnostic, with support for Entity Framework Core, MongoDB, and Dapper out of the box.
  • Elsa Studio: a modular Blazor dashboard app for managing and designing workflows.

Roadmap

See ROADMAP.md for the current roadmap and #3232 for historical roadmap discussion.

Use Cases

Elsa can be used in a variety of scenarios, including:

  • Long-running workflows such as order fulfillment and product approval.
  • Short-running workflows such as sending emails and generating PDFs.
  • Scheduled workflows such as sending daily reports.
  • Event-driven workflows such as sending welcome emails when a user signs up.

Coding Workflows

Elsa allows you to define workflows in code using C#. The following example shows how to receive HTTP requests and send an email in response:

public class SendEmailWorkflow : WorkflowBase
{
    protected override void Build(IWorkflowBuilder builder)
    {
        builder.Root = new Sequence
        {
            Activities =
            {
                new HttpEndpoint
                {
                    Path = new("/send-email"),
                    SupportedMethods = new(new[] { HttpMethods.Post }),
                    CanStartWorkflow = true
                },
                new SendEmail
                {
                    From = new("alic@acme.com"),
                    To = new(new[]{ "bob@acme.com" }),
                    Subject = new("Your workflow has been triggered!"),
                    Body = new("Hello!")
                }
            }
        };
    }
}

Designing Workflows

Elsa allows you to define workflows using a visual designer. The following example shows how to receive HTTP requests and send an email in response:

Elsa ships with a powerful visual designer

Contributing

We welcome contributions from the community and are pleased that you are interested in helping to improve the Elsa Workflow project! Here are the steps to contribute to our project:

1. Fork and Clone the Repo

To get started, you'll need to fork the repository to your own GitHub account. You can do this by navigating to the Elsa Workflow GitHub repository and clicking the "Fork" button in the top-right corner of the page. Once you have forked the repo, you can clone it to your local machine using the following command:

git clone https://github.com/YOUR_USERNAME/elsa-core.git

Replace YOUR_USERNAME with your GitHub username. For more information on forking a repo, check out the GitHub documentation here.

Incorporating the details about the "apps" folder and its projects into the second point about opening the Elsa.sln using your favorite IDE, we can expand the instructions to guide developers on where to start and what projects they might want to explore first. Here's an updated version of that section with the additional information:

2. Open Elsa.sln Using Your Favorite IDE

After cloning the repository, navigate to the cloned directory and open the Elsa.sln solution file with your preferred IDE that supports .NET development, such as Visual Studio, JetBrains Rider, or Visual Studio Code with the appropriate extensions.

Within the solution, you will find an "apps" folder containing three projects designed to help you get started and explore the capabilities of Elsa Workflow:

  • Elsa.Server.Web: This project is a reference ASP.NET Core application that acts as a workflow server. It's a great starting point if you want to understand how Elsa functions as a server-side workflow engine.

  • Elsa.ServerAndStudio.Web: This project serves a dual purpose. Like Elsa.Server.Web, it acts as a workflow server. Additionally, it hosts the Elsa Studio Blazor WebAssembly app. This is the perfect project to run if you want to see the full capabilities of Elsa, including both the server aspects and the client-side studio experience in one application.

  • Elsa.Studio.Web: This project is a reference Blazor WebAssembly application that solely hosts the Elsa Studio Blazor WebAssembly app. It requires a running Elsa server application to connect to. Use this project if you're interested in focusing on the Elsa Studio UI and its interactions with an Elsa workflow server.

3. Submit a PR with Your Changes

Once you have made your changes, commit them and push them back to your fork. Then, navigate to the original Elsa Workflow repository and create a new Pull Request. Ensure your PR description clearly describes the changes and any relevant information that will help the reviewers understand your contributions. For a detailed guide on creating a pull request, visit Creating a pull request from a fork.

4. Open an Issue First

Before you start working on your changes or submit a pull request, please open an issue to discuss what you would like to do. This step is crucial as it ensures you don't spend time working on something that might not align with the project's goals or might already be under development by someone else. You can open an issue here.

This approach helps us streamline contributions and ensures that your efforts are aligned with the project's needs and priorities. We look forward to your contributions and are here to support you throughout the process. Thank you for contributing to the Elsa Workflow project!

Support

There are various ways to get support for Elsa Workflows, ranging from community-driven channels to enterprise-level services.

Community Support

Elsa has an active and helpful community where you can find support through multiple channels:

  • GitHub Issues for bug reports and feature requests.
  • GitHub Discussions for open-ended conversations, questions, and community-driven support.
  • Discord for real-time support and interaction with the Elsa community.
  • StackOverflow for searching or asking technical questions.

Professional Support

For organizations requiring professional support and long-term commitment, check out Elsa+, a growing ecosystem of premium services, tooling, and extensions around Elsa Workflows.