diff --git a/csharp/ql/lib/change-notes/2025-02-03-blazor-routing-parameters.md b/csharp/ql/lib/change-notes/2025-02-03-blazor-routing-parameters.md
new file mode 100644
index 000000000000..00afc5867c62
--- /dev/null
+++ b/csharp/ql/lib/change-notes/2025-02-03-blazor-routing-parameters.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* Blazor `[Parameter]` fields bound to a variable from the route specified in the `@page` directive are now modeled as remote flow sources.
diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/microsoft/aspnetcore/Components.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/microsoft/aspnetcore/Components.qll
new file mode 100644
index 000000000000..6e37fc0480fb
--- /dev/null
+++ b/csharp/ql/lib/semmle/code/csharp/frameworks/microsoft/aspnetcore/Components.qll
@@ -0,0 +1,135 @@
+/** Provides classes for working with `Microsoft.AspNetCore.Components` */
+
+import csharp
+import semmle.code.csharp.frameworks.Microsoft
+import semmle.code.csharp.frameworks.microsoft.AspNetCore
+
+/** The `Microsoft.AspNetCore.Components` namespace */
+class MicrosoftAspNetCoreComponentsNamespace extends Namespace {
+ MicrosoftAspNetCoreComponentsNamespace() {
+ this.getParentNamespace() instanceof MicrosoftAspNetCoreNamespace and
+ this.hasName("Components")
+ }
+}
+
+/**
+ * A class in the `Microsoft.AspNetCore.Components` namespace.
+ */
+private class MicrosoftAspNetCoreComponentsClass extends Class {
+ MicrosoftAspNetCoreComponentsClass() {
+ this.getNamespace() instanceof MicrosoftAspNetCoreComponentsNamespace
+ }
+}
+
+/** The `Microsoft.AspNetCore.Components.CascadingParameterAttributeBase` class. */
+class MicrosoftAspNetCoreComponentsCascadingParameterAttributeBaseClass extends MicrosoftAspNetCoreComponentsClass
+{
+ MicrosoftAspNetCoreComponentsCascadingParameterAttributeBaseClass() {
+ this.hasName("CascadingParameterAttributeBase")
+ }
+}
+
+/** The `Microsoft.AspNetCore.Components.ComponentBase` class. */
+class MicrosoftAspNetCoreComponentsComponentBaseClass extends MicrosoftAspNetCoreComponentsClass {
+ MicrosoftAspNetCoreComponentsComponentBaseClass() { this.hasName("ComponentBase") }
+}
+
+/** The `Microsoft.AspNetCore.Components.IComponent` interface. */
+class MicrosoftAspNetCoreComponentsIComponentInterface extends Interface {
+ MicrosoftAspNetCoreComponentsIComponentInterface() {
+ this.getNamespace() instanceof MicrosoftAspNetCoreComponentsNamespace and
+ this.hasName("IComponent")
+ }
+}
+
+/** The `Microsoft.AspNetCore.Components.RouteAttribute` attribute. */
+private class MicrosoftAspNetCoreComponentsRouteAttribute extends Attribute {
+ MicrosoftAspNetCoreComponentsRouteAttribute() {
+ this.getType().getNamespace() instanceof MicrosoftAspNetCoreComponentsNamespace and
+ this.getType().hasName("RouteAttribute")
+ }
+}
+
+/** The `Microsoft.AspNetCore.Components.ParameterAttribute` attribute. */
+private class MicrosoftAspNetCoreComponentsParameterAttribute extends Attribute {
+ MicrosoftAspNetCoreComponentsParameterAttribute() {
+ this.getType().getNamespace() instanceof MicrosoftAspNetCoreComponentsNamespace and
+ this.getType().hasName("ParameterAttribute")
+ }
+}
+
+/** An ASP.NET Core (Blazor) component. */
+class MicrosoftAspNetCoreComponentsComponent extends Class {
+ MicrosoftAspNetCoreComponentsComponent() {
+ this.getABaseType+() instanceof MicrosoftAspNetCoreComponentsComponentBaseClass or
+ this.getABaseType+() instanceof MicrosoftAspNetCoreComponentsIComponentInterface
+ }
+
+ /** Gets a property whose value cascades down the component hierarchy. */
+ Property getACascadingParameterProperty() {
+ result = this.getAProperty() and
+ result.getAnAttribute().getType().getBaseClass() instanceof
+ MicrosoftAspNetCoreComponentsCascadingParameterAttributeBaseClass
+ }
+
+ /** Gets the url for the route from the `Microsoft.AspNetCore.Components.RouteAttribute` of the component. */
+ private string getRouteAttributeUrl() {
+ exists(MicrosoftAspNetCoreComponentsRouteAttribute a | a = this.getAnAttribute() |
+ result = a.getArgument(0).getValue()
+ )
+ }
+
+ /**
+ * Gets a route parameter from the `Microsoft.AspNetCore.Components.RouteAttribute` of the component.
+ *
+ * A route parameter is defined in the URL by wrapping its name in a pair of { braces } when adding a component's @page declaration.
+ * There are various extensions that can be added next to the parameter name, such as `:int` or `?` to make the parameter optional.
+ * Optionally, the parameter name can start with a `*` to make it a catch-all parameter.
+ *
+ * An example of a route parameter is `@page "/counter/{id:int}/{other?}/{*rest}"`, from this we're getting the `id`, `other` and `rest` parameters.
+ */
+ pragma[nomagic]
+ private string getARouteParameter() {
+ exists(string s |
+ s = this.getRouteAttributeUrl().splitAt("{").regexpCapture("\\*?([^:?}]+)[:?}](.*)", 1) and
+ result = s.toLowerCase()
+ )
+ }
+
+ /** Gets a property attributed with `[Parameter]` attribute. */
+ pragma[nomagic]
+ private Property getAParameterProperty(string name) {
+ result = this.getAProperty() and
+ result.getAnAttribute() instanceof MicrosoftAspNetCoreComponentsParameterAttribute and
+ name = result.getName().toLowerCase()
+ }
+
+ /** Gets a property whose value is populated from route parameters. */
+ Property getARouteParameterProperty() {
+ exists(string name | name = this.getARouteParameter() |
+ result = this.getAParameterProperty(name)
+ )
+ }
+}
+
+private module Sources {
+ private import semmle.code.csharp.security.dataflow.flowsources.Remote
+
+ /**
+ * A property with a `[Parameter]` attribute in an ASP.NET Core component which
+ * is populated from a route parameter.
+ */
+ private class AspNetCoreComponentRouteParameterFlowSource extends AspNetRemoteFlowSource,
+ DataFlow::ExprNode
+ {
+ AspNetCoreComponentRouteParameterFlowSource() {
+ exists(MicrosoftAspNetCoreComponentsComponent c, Property p |
+ p = c.getARouteParameterProperty()
+ |
+ this.asExpr() = p.getGetter().getACall()
+ )
+ }
+
+ override string getSourceType() { result = "ASP.NET Core component route parameter" }
+ }
+}
diff --git a/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsources/Remote.qll b/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsources/Remote.qll
index dc2fb36c47ae..2906fde4e1de 100644
--- a/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsources/Remote.qll
+++ b/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsources/Remote.qll
@@ -26,7 +26,8 @@ abstract class RemoteFlowSource extends SourceNode {
* A module for importing frameworks that defines remote flow sources.
*/
private module RemoteFlowSources {
- private import semmle.code.csharp.frameworks.ServiceStack
+ private import semmle.code.csharp.frameworks.ServiceStack as ServiceStack
+ private import semmle.code.csharp.frameworks.microsoft.aspnetcore.Components as Blazor
}
/** A data flow source of remote user input (ASP.NET). */
diff --git a/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/Components_MyInput_razor.g.cs b/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/Components_MyInput_razor.g.cs
new file mode 100644
index 000000000000..bdff55cfc1cc
--- /dev/null
+++ b/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/Components_MyInput_razor.g.cs
@@ -0,0 +1,124 @@
+//
+#pragma warning disable 1591
+namespace BlazorTest.Components
+{
+#line default
+ using global::System;
+ using global::System.Collections.Generic;
+ using global::System.Linq;
+ using global::System.Threading.Tasks;
+ using global::Microsoft.AspNetCore.Components;
+#nullable restore
+ using System.Net.Http
+
+#nullable disable
+ ;
+#nullable restore
+ using System.Net.Http.Json
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.AspNetCore.Components.Forms
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.AspNetCore.Components.Routing
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.AspNetCore.Components.Web
+
+#nullable disable
+ ;
+#nullable restore
+ using static Microsoft.AspNetCore.Components.Web.RenderMode
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.AspNetCore.Components.Web.Virtualization
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.JSInterop
+
+#nullable disable
+ ;
+#nullable restore
+ using BlazorTest
+
+#nullable disable
+ ;
+#nullable restore
+ using BlazorTest.Components
+
+#line default
+#line hidden
+#nullable disable
+ ;
+ [global::BlazorTest.Components.MyInput.__PrivateComponentRenderModeAttribute]
+#nullable restore
+ public partial class MyInput : global::Microsoft.AspNetCore.Components.ComponentBase
+#nullable disable
+ {
+#pragma warning disable 1998
+ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
+ {
+ __builder.OpenElement(0, "input");
+ __builder.AddAttribute(1, "value", global::Microsoft.AspNetCore.Components.BindConverter.FormatValue(
+#nullable restore
+Param1
+
+#line default
+#line hidden
+#nullable disable
+ ));
+ __builder.AddAttribute(2, "onchange", global::Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredBindSetter(callback: __value =>
+ {
+ Param1 = __value; return global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.InvokeAsynchronousDelegate(callback:
+#nullable restore
+Fire
+
+#line default
+#line hidden
+#nullable disable
+ );
+ }, value: Param1), Param1));
+ __builder.SetUpdatesAttributeName("value");
+ __builder.CloseElement();
+ }
+#pragma warning restore 1998
+#nullable restore
+
+ [Parameter]
+ public string? Param1 { get; set; } = "";
+
+ [Parameter]
+ public EventCallback ValueChanged { get; set; }
+
+ [Parameter]
+ public EventCallback Param1Changed { get; set; }
+
+ private void Fire()
+ {
+ ValueChanged.InvokeAsync(Param1);
+ Param1Changed.InvokeAsync(Param1);
+ }
+
+#line default
+#line hidden
+#nullable disable
+
+ private sealed class __PrivateComponentRenderModeAttribute : global::Microsoft.AspNetCore.Components.RenderModeAttribute
+ {
+ private static global::Microsoft.AspNetCore.Components.IComponentRenderMode ModeImpl => InteractiveServer
+ ;
+ public override global::Microsoft.AspNetCore.Components.IComponentRenderMode Mode => ModeImpl;
+ }
+ }
+}
+#pragma warning restore 1591
diff --git a/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/Components_MyOutput_razor.g.cs b/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/Components_MyOutput_razor.g.cs
new file mode 100644
index 000000000000..698aceba27cf
--- /dev/null
+++ b/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/Components_MyOutput_razor.g.cs
@@ -0,0 +1,115 @@
+//
+#pragma warning disable 1591
+namespace BlazorTest.Components
+{
+#line default
+ using global::System;
+ using global::System.Collections.Generic;
+ using global::System.Linq;
+ using global::System.Threading.Tasks;
+ using global::Microsoft.AspNetCore.Components;
+#nullable restore
+ using System.Net.Http
+
+#nullable disable
+ ;
+#nullable restore
+ using System.Net.Http.Json
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.AspNetCore.Components.Forms
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.AspNetCore.Components.Routing
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.AspNetCore.Components.Web
+
+#nullable disable
+ ;
+#nullable restore
+ using static Microsoft.AspNetCore.Components.Web.RenderMode
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.AspNetCore.Components.Web.Virtualization
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.JSInterop
+
+#nullable disable
+ ;
+#nullable restore
+ using BlazorTest
+
+#nullable disable
+ ;
+#nullable restore
+ using BlazorTest.Components
+
+#line default
+#line hidden
+#nullable disable
+ ;
+ [global::BlazorTest.Components.MyOutput.__PrivateComponentRenderModeAttribute]
+#nullable restore
+ public partial class MyOutput : global::Microsoft.AspNetCore.Components.ComponentBase
+#nullable disable
+ {
+#pragma warning disable 1998
+ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
+ {
+ __builder.OpenElement(0, "div");
+ __builder.OpenElement(1, "p");
+ __builder.AddContent(2, "Value from InputText: ");
+ __builder.AddContent(3,
+#nullable restore
+Value
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.AddMarkupContent(4, "\n ");
+ __builder.OpenElement(5, "p");
+ __builder.AddContent(6, "Raw value from InputText: ");
+ __builder.AddContent(7,
+#nullable restore
+new MarkupString(Value)
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.CloseElement();
+ }
+#pragma warning restore 1998
+#nullable restore
+
+ [Parameter]
+ public string Value { get; set; } = "";
+
+#line default
+#line hidden
+#nullable disable
+
+ private sealed class __PrivateComponentRenderModeAttribute : global::Microsoft.AspNetCore.Components.RenderModeAttribute
+ {
+ private static global::Microsoft.AspNetCore.Components.IComponentRenderMode ModeImpl => InteractiveServer
+ ;
+ public override global::Microsoft.AspNetCore.Components.IComponentRenderMode Mode => ModeImpl;
+ }
+ }
+}
+#pragma warning restore 1591
diff --git a/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/Components_Pages_TestPage_razor.g.cs b/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/Components_Pages_TestPage_razor.g.cs
new file mode 100644
index 000000000000..8732f8c7e4be
--- /dev/null
+++ b/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/Components_Pages_TestPage_razor.g.cs
@@ -0,0 +1,567 @@
+//
+#pragma warning disable 1591
+namespace BlazorTest.Components.Pages
+{
+#line default
+ using global::System;
+ using global::System.Collections.Generic;
+ using global::System.Linq;
+ using global::System.Threading.Tasks;
+ using global::Microsoft.AspNetCore.Components;
+#nullable restore
+ using System.Net.Http
+
+#nullable disable
+ ;
+#nullable restore
+ using System.Net.Http.Json
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.AspNetCore.Components.Forms
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.AspNetCore.Components.Routing
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.AspNetCore.Components.Web
+
+#nullable disable
+ ;
+#nullable restore
+ using static Microsoft.AspNetCore.Components.Web.RenderMode
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.AspNetCore.Components.Web.Virtualization
+
+#nullable disable
+ ;
+#nullable restore
+ using Microsoft.JSInterop
+
+#nullable disable
+ ;
+#nullable restore
+ using BlazorTest
+
+#nullable disable
+ ;
+#nullable restore
+ using BlazorTest.Components
+
+#line default
+#line hidden
+#nullable disable
+ ;
+ [global::Microsoft.AspNetCore.Components.RouteAttribute(
+ // language=Route,Component
+#nullable restore
+"/"
+
+#line default
+#line hidden
+#nullable disable
+ )]
+ [global::Microsoft.AspNetCore.Components.RouteAttribute(
+ // language=Route,Component
+#nullable restore
+"/test/{urlParam?}"
+
+#line default
+#line hidden
+#nullable disable
+ )]
+ [global::BlazorTest.Components.Pages.TestPage.__PrivateComponentRenderModeAttribute]
+#nullable restore
+ public partial class TestPage : global::Microsoft.AspNetCore.Components.ComponentBase
+#nullable disable
+ {
+#pragma warning disable 1998
+ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
+ {
+ __builder.OpenComponent(0);
+ __builder.AddAttribute(1, "ChildContent", (global::Microsoft.AspNetCore.Components.RenderFragment)((__builder2) =>
+ {
+ __builder2.AddContent(2, "TestPage");
+ }
+ ));
+ __builder.CloseComponent();
+ __builder.AddMarkupContent(3, "\n\n");
+ __builder.OpenElement(4, "div");
+ __builder.AddMarkupContent(5, "Route parameter
\n ");
+ __builder.OpenElement(6, "p");
+ __builder.AddContent(7, "Go to: ");
+ __builder.OpenElement(8, "a");
+ __builder.AddAttribute(9, "href", "/test/" + (
+#nullable restore
+XssUrl
+
+#line default
+#line hidden
+#nullable disable
+ ));
+ __builder.AddContent(10, "/test/");
+ __builder.AddContent(11,
+#nullable restore
+XssUrl
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.CloseElement();
+ __builder.AddMarkupContent(12, "\n ");
+ __builder.OpenElement(13, "p");
+ __builder.AddContent(14, "Parameter from URL: ");
+ __builder.AddContent(15,
+#nullable restore
+UrlParam
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.AddMarkupContent(16, "\n ");
+ __builder.OpenElement(17, "p");
+ __builder.AddContent(18, "Raw parameter from URL: ");
+ __builder.AddContent(19,
+#nullable restore
+(MarkupString)UrlParam
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.CloseElement();
+ __builder.AddMarkupContent(20, "\n\n
\n\n");
+ __builder.OpenElement(21, "div");
+ __builder.AddMarkupContent(22, "Query parameter
\n ");
+ __builder.OpenElement(23, "p");
+ __builder.AddContent(24, "Go to: ");
+ __builder.OpenElement(25, "a");
+ __builder.AddAttribute(26, "href", "/test/?qs=" + (
+#nullable restore
+XssUrl
+
+#line default
+#line hidden
+#nullable disable
+ ));
+ __builder.AddContent(27, "/test/?qs=");
+ __builder.AddContent(28,
+#nullable restore
+XssUrl
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.CloseElement();
+ __builder.AddMarkupContent(29, "\n ");
+ __builder.OpenElement(30, "p");
+ __builder.AddContent(31, "Parameter from query string: ");
+ __builder.AddContent(32,
+#nullable restore
+QueryParam
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.AddMarkupContent(33, "\n ");
+ __builder.OpenElement(34, "p");
+ __builder.AddContent(35, "Raw parameter from query string: ");
+ __builder.AddContent(36,
+#nullable restore
+new MarkupString(QueryParam)
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.CloseElement();
+ __builder.AddMarkupContent(37, "\n\n
\n\n");
+ __builder.OpenElement(38, "div");
+ __builder.AddMarkupContent(39, "Bind InputText component
\n ");
+ __builder.OpenComponent(40);
+ __builder.AddComponentParameter(41, nameof(global::Microsoft.AspNetCore.Components.Forms.InputText.
+#nullable restore
+Value
+
+#line default
+#line hidden
+#nullable disable
+ ), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck(
+#nullable restore
+InputValue1
+
+#line default
+#line hidden
+#nullable disable
+ ));
+ __builder.AddComponentParameter(42, nameof(global::Microsoft.AspNetCore.Components.Forms.InputText.ValueChanged), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck>(global::Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(this, __value => InputValue1 = __value, InputValue1))));
+ __builder.AddComponentParameter(43, nameof(global::Microsoft.AspNetCore.Components.Forms.InputText.ValueExpression), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck>>(() => InputValue1));
+ __builder.CloseComponent();
+ __builder.AddMarkupContent(44, "\n ");
+ __builder.OpenElement(45, "p");
+ __builder.AddContent(46, "Value from InputText: ");
+ __builder.AddContent(47,
+#nullable restore
+InputValue1
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.AddMarkupContent(48, "\n ");
+ __builder.OpenElement(49, "p");
+ __builder.AddContent(50, "Raw value from InputText: ");
+ __builder.AddContent(51,
+#nullable restore
+new MarkupString(InputValue1)
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.CloseElement();
+ __builder.AddMarkupContent(52, "\n\n
\n\n");
+ __builder.OpenElement(53, "div");
+ __builder.AddMarkupContent(54, "Bind input element
\n ");
+ __builder.OpenElement(55, "input");
+ __builder.AddAttribute(56, "value", global::Microsoft.AspNetCore.Components.BindConverter.FormatValue(
+#nullable restore
+InputValue2
+
+#line default
+#line hidden
+#nullable disable
+ ));
+ __builder.AddAttribute(57, "onchange", global::Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => InputValue2 = __value, InputValue2));
+ __builder.SetUpdatesAttributeName("value");
+ __builder.CloseElement();
+ __builder.AddMarkupContent(58, "\n ");
+ __builder.OpenElement(59, "p");
+ __builder.AddContent(60, "Value from InputText: ");
+ __builder.AddContent(61,
+#nullable restore
+InputValue2
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.AddMarkupContent(62, "\n ");
+ __builder.OpenElement(63, "p");
+ __builder.AddContent(64, "Raw value from InputText: ");
+ __builder.AddContent(65,
+#nullable restore
+new MarkupString(InputValue2)
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.CloseElement();
+ __builder.AddMarkupContent(66, "\n\n
\n\n");
+ __builder.OpenElement(67, "div");
+ __builder.AddMarkupContent(68, "Bind through object property
\n ");
+ __builder.OpenElement(69, "input");
+ __builder.AddAttribute(70, "value", global::Microsoft.AspNetCore.Components.BindConverter.FormatValue(
+#nullable restore
+Container1.Value
+
+#line default
+#line hidden
+#nullable disable
+ ));
+ __builder.AddAttribute(71, "onchange", global::Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => Container1.Value = __value, Container1.Value));
+ __builder.SetUpdatesAttributeName("value");
+ __builder.CloseElement();
+ __builder.AddMarkupContent(72, "\n ");
+ __builder.OpenElement(73, "p");
+ __builder.AddContent(74, "Value from InputText: ");
+ __builder.AddContent(75,
+#nullable restore
+Container1.Value
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.AddMarkupContent(76, "\n ");
+ __builder.OpenElement(77, "p");
+ __builder.AddContent(78, "Raw value from InputText: ");
+ __builder.AddContent(79,
+#nullable restore
+new MarkupString(Container1.Value)
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.CloseElement();
+ __builder.AddMarkupContent(80, "\n\n
\n\n");
+ __builder.OpenElement(81, "div");
+ __builder.AddMarkupContent(82, "Input component with custom event
\n ");
+ __builder.OpenComponent(83);
+ __builder.AddComponentParameter(84, nameof(global::BlazorTest.Components.MyInput.
+#nullable restore
+Param1
+
+#line default
+#line hidden
+#nullable disable
+ ), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck(
+#nullable restore
+InputValue3
+
+#line default
+#line hidden
+#nullable disable
+ ));
+ __builder.AddComponentParameter(85, nameof(global::BlazorTest.Components.MyInput.
+#nullable restore
+ValueChanged
+
+#line default
+#line hidden
+#nullable disable
+ ), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck>(global::Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this,
+#nullable restore
+MyInputChanged
+
+#line default
+#line hidden
+#nullable disable
+ )));
+ __builder.CloseComponent();
+ __builder.AddMarkupContent(86, "\n ");
+ __builder.OpenElement(87, "p");
+ __builder.AddContent(88, "Value from InputText: ");
+ __builder.AddContent(89,
+#nullable restore
+InputValue3
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.AddMarkupContent(90, "\n ");
+ __builder.OpenElement(91, "p");
+ __builder.AddContent(92, "Raw value from InputText: ");
+ __builder.AddContent(93,
+#nullable restore
+new MarkupString(InputValue3)
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.CloseElement();
+ __builder.AddMarkupContent(94, "\n\n
\n\n");
+ __builder.OpenElement(95, "div");
+ __builder.AddMarkupContent(96, "Input component with binding
\n ");
+ __builder.OpenComponent(97);
+ __builder.AddComponentParameter(98, nameof(global::BlazorTest.Components.MyInput.
+#nullable restore
+Param1
+
+#line default
+#line hidden
+#nullable disable
+ ), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck(
+#nullable restore
+InputValue4
+
+#line default
+#line hidden
+#nullable disable
+ ));
+ __builder.AddComponentParameter(99, nameof(global::BlazorTest.Components.MyInput.Param1Changed), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck>(global::Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(this, __value => InputValue4 = __value, InputValue4))));
+ __builder.CloseComponent();
+ __builder.AddMarkupContent(100, "\n ");
+ __builder.OpenElement(101, "p");
+ __builder.AddContent(102, "Value from InputText: ");
+ __builder.AddContent(103,
+#nullable restore
+InputValue4
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.AddMarkupContent(104, "\n ");
+ __builder.OpenElement(105, "p");
+ __builder.AddContent(106, "Raw value from InputText: ");
+ __builder.AddContent(107,
+#nullable restore
+new MarkupString(InputValue4)
+
+#line default
+#line hidden
+#nullable disable
+ );
+ __builder.CloseElement();
+ __builder.CloseElement();
+ __builder.AddMarkupContent(108, "\n\n
\n\n");
+ __builder.OpenElement(109, "div");
+ __builder.AddMarkupContent(110, "Input, Output components
\n ");
+ __builder.OpenComponent(111);
+ __builder.AddComponentParameter(112, nameof(global::BlazorTest.Components.MyInput.
+#nullable restore
+Param1
+
+#line default
+#line hidden
+#nullable disable
+ ), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck(
+#nullable restore
+InputValue5
+
+#line default
+#line hidden
+#nullable disable
+ ));
+ __builder.AddComponentParameter(113, nameof(global::BlazorTest.Components.MyInput.Param1Changed), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck>(global::Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(this, __value => InputValue5 = __value, InputValue5))));
+ __builder.CloseComponent();
+ __builder.AddMarkupContent(114, "\n ");
+ __builder.OpenComponent(115);
+ __builder.AddComponentParameter(116, nameof(global::BlazorTest.Components.MyOutput.
+#nullable restore
+Value
+
+#line default
+#line hidden
+#nullable disable
+ ), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck(
+#nullable restore
+InputValue5
+
+#line default
+#line hidden
+#nullable disable
+ ));
+ __builder.CloseComponent();
+ __builder.CloseElement();
+ __builder.AddMarkupContent(117, "\n\n
\n\n");
+ __builder.OpenElement(118, "div");
+ __builder.AddMarkupContent(119, "Bind InputText, Output component
\n ");
+ __builder.OpenComponent(120);
+ __builder.AddComponentParameter(121, nameof(global::Microsoft.AspNetCore.Components.Forms.InputText.
+#nullable restore
+Value
+
+#line default
+#line hidden
+#nullable disable
+ ), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck(
+#nullable restore
+InputValue6
+
+#line default
+#line hidden
+#nullable disable
+ ));
+ __builder.AddComponentParameter(122, nameof(global::Microsoft.AspNetCore.Components.Forms.InputText.ValueChanged), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck>(global::Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(this, __value => InputValue6 = __value, InputValue6))));
+ __builder.AddComponentParameter(123, nameof(global::Microsoft.AspNetCore.Components.Forms.InputText.ValueExpression), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck>>(() => InputValue6));
+ __builder.CloseComponent();
+ __builder.AddMarkupContent(124, "\n ");
+ __builder.OpenComponent(125);
+ __builder.AddComponentParameter(126, nameof(global::BlazorTest.Components.MyOutput.
+#nullable restore
+Value
+
+#line default
+#line hidden
+#nullable disable
+ ), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck(
+#nullable restore
+InputValue6
+
+#line default
+#line hidden
+#nullable disable
+ ));
+ __builder.CloseComponent();
+ __builder.CloseElement();
+ }
+#pragma warning restore 1998
+#nullable restore
+
+
+ public class Container
+ {
+ public string? Value { get; set; } = "";
+ }
+
+ private const string XssUrl = "aaaa<%2Fb>";
+ private const string XssUrl2 = "aaaa";
+
+ [Parameter]
+ public string UrlParam { get; set; } = "";
+
+ [SupplyParameterFromQuery(Name = "qs")]
+ public string QueryParam { get; set; } = "";
+
+ public string InputValue1 { get; set; } = "";
+ public string InputValue2 { get; set; } = "";
+ public string InputValue3 { get; set; } = "";
+ public string InputValue4 { get; set; } = "";
+ public string InputValue5 { get; set; } = "";
+ public string InputValue6 { get; set; } = "";
+
+ public Container Container1 { get; set; } = new Container();
+
+ protected override void OnInitialized()
+ {
+ InputValue1 = XssUrl2;
+ InputValue2 = XssUrl2;
+ Container1.Value = XssUrl2;
+ InputValue3 = XssUrl2;
+ InputValue4 = XssUrl2;
+ InputValue5 = XssUrl2;
+ InputValue6 = XssUrl2;
+
+ }
+
+ private void MyInputChanged(string value)
+ {
+ InputValue3 = value;
+ }
+
+#line default
+#line hidden
+#nullable disable
+
+ private sealed class __PrivateComponentRenderModeAttribute : global::Microsoft.AspNetCore.Components.RenderModeAttribute
+ {
+ private static global::Microsoft.AspNetCore.Components.IComponentRenderMode ModeImpl => InteractiveServer
+ ;
+ public override global::Microsoft.AspNetCore.Components.IComponentRenderMode Mode => ModeImpl;
+ }
+ }
+}
+#pragma warning restore 1591
diff --git a/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/options b/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/options
new file mode 100644
index 000000000000..d44a4aa5500b
--- /dev/null
+++ b/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/options
@@ -0,0 +1,2 @@
+semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../../resources/stubs/_frameworks/Microsoft.AspNetCore.App/Microsoft.AspNetCore.App.csproj
+semmle-extractor-options: /nostdlib /noconfig
\ No newline at end of file
diff --git a/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/remoteFlowSource.expected b/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/remoteFlowSource.expected
new file mode 100644
index 000000000000..2c845e8e4001
--- /dev/null
+++ b/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/remoteFlowSource.expected
@@ -0,0 +1,4 @@
+| Components_Pages_TestPage_razor.g.cs:126:1:126:8 | access to property UrlParam | ASP.NET Core component route parameter |
+| Components_Pages_TestPage_razor.g.cs:138:15:138:22 | access to property UrlParam | ASP.NET Core component route parameter |
+| Components_Pages_TestPage_razor.g.cs:176:1:176:10 | access to property QueryParam | external |
+| Components_Pages_TestPage_razor.g.cs:188:18:188:27 | access to property QueryParam | external |
diff --git a/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/remoteFlowSource.ql b/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/remoteFlowSource.ql
new file mode 100644
index 000000000000..afccffd11fec
--- /dev/null
+++ b/csharp/ql/test/library-tests/frameworks/microsoft/aspnetcore/blazor/remoteFlowSource.ql
@@ -0,0 +1,7 @@
+import semmle.code.csharp.security.dataflow.flowsources.Remote
+
+from RemoteFlowSource source, File f
+where
+ source.getLocation().getFile() = f and
+ f.fromSource()
+select source, source.getSourceType()