Activities of "maliming"

Hi,

You're right — the native <input type="file"> button text is drawn by the browser itself, so the app's localization can't reach it. The Account Pro module should handle this rather than leaving the inconsistency for each project to work around.

We'll apply the same pattern you've already adopted in your Angular app: hide the native input, show a localized button (AbpAccount::ChoosePhoto) plus a read-only label that shows the selected file name or AbpAccount::NoFileChosen. The Angular profile-picture component and the MVC ProfilePicture view component will both get this treatment so the page is fully localized out of the box.

The fix is already prepared on our side and will land in an upcoming release.

Thanks

Hi,

Could you share the following so we can pinpoint it:

  1. The full ABP Studio Output / log content captured during the Suite "Save and generate" run — full stdout/stderr, especially any line starting with Running the Angular Schematics command: and everything after it.
  2. A screenshot of your microservice solution's apps/ folder, plus the contents of apps/angular/ (we want to confirm angular.json and package.json are there).
  3. The contents of .suite/config.json under the microservice solution root, if one exists.
  4. A screenshot of the Suite entity form right before you click "Save and generate", so we can see the toggles (Create UI / Use Angular Schematics / etc.).
  5. Output of node -v, npm -v, and (from apps/angular/) npx ng version.
  6. The OS you're running Suite on (Windows / macOS / Linux + version), and the ABP Suite version (abp suite --version).
  7. The .sln (or .abpsln) path you opened in ABP Studio when launching Suite, and whether you used "Add new microservice" at some point to add the TransactionService.
  8. Was Angular page generation working for any earlier entity in this same solution, or is InvestmentCompany the first one you tried?
  9. Does apps/angular build/run cleanly on its own (e.g. yarn install && yarn start) before Suite is involved?

If the project is small enough, a zip of the solution (or a wetransfer link) sent to liming.ma@volosoft.com is the fastest path — we can reproduce locally and tell you exactly which step failed.

Thanks

: )

Answer

ok : )

Hi,

Confirmed — the ?sorting= query parameter on every list endpoint that consumes ISortedResultRequest flows straight into IQueryable.OrderBy(string) from System.Linq.Dynamic.Core. The library's RestrictOrderByToPropertyOrField default in 1.6.0+ only blocks top-level keywords like IIF(...) and Substring(x, ...); it does not block method calls on a property (PasswordHash.Substring(0, 1)), comparison expressions (PasswordHash > "X") or chained predicates. EF Core translates those into real ORDER BY SUBSTRING(...) / CASE WHEN ... THEN 1 ELSE 0 END SQL, so an authenticated user with list access can byte-extract sensitive columns through the row ordering, even when those columns are never in the response DTO.

The fix has to sit outside Dynamic.Core. ExtensibilityPoint.QueryOptimizer is the public static hook the library exposes for inspecting expanded query trees; it fires on every OrderBy(string) and ThenBy(string) call regardless of which layer the caller sits in (MVC, Blazor, background job, direct DI call), so a single registration covers all of your list endpoints at once.

Drop this class into your *.HttpApi.Host (any host that boots AbpDddApplicationModule works — adjust the namespace to fit your project):

using System;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using Volo.Abp.Validation;

namespace MyCompanyName.MyProjectName;

public static class DynamicSortingGuard
{
    private static readonly object InstallLock = new();
    private static Func<Expression, Expression> _activeOptimizer;

    public static void Install()
    {
        lock (InstallLock)
        {
            var current = ExtensibilityPoint.QueryOptimizer;
            if (_activeOptimizer != null && ReferenceEquals(current, _activeOptimizer))
            {
                return;
            }

            var previous = current;
            _activeOptimizer = expression =>
            {
                new OrderByMethodVisitor().Visit(expression);
                return previous != null ? previous(expression) : expression;
            };
            ExtensibilityPoint.QueryOptimizer = _activeOptimizer;
        }
    }

    private sealed class OrderByMethodVisitor : ExpressionVisitor
    {
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (node.Method.DeclaringType == typeof(Queryable) &&
                IsOrderByMethod(node.Method.Name) &&
                node.Arguments.Count >= 2 &&
                node.Arguments[1] is UnaryExpression { Operand: LambdaExpression lambda })
            {
                new PropertyOnlySelectorVisitor().Visit(lambda.Body);
            }

            return base.VisitMethodCall(node);
        }

        private static bool IsOrderByMethod(string name)
        {
            return name == nameof(Queryable.OrderBy)
                || name == nameof(Queryable.OrderByDescending)
                || name == nameof(Queryable.ThenBy)
                || name == nameof(Queryable.ThenByDescending);
        }
    }

    private sealed class PropertyOnlySelectorVisitor : ExpressionVisitor
    {
        private const string Message = "Sorting expression is not supported.";

        protected override Expression VisitMethodCall(MethodCallExpression node)
            => throw new AbpValidationException(Message);

        protected override Expression VisitBinary(BinaryExpression node)
            => throw new AbpValidationException(Message);

        protected override Expression VisitConditional(ConditionalExpression node)
            => throw new AbpValidationException(Message);

        protected override Expression VisitConstant(ConstantExpression node)
            => throw new AbpValidationException(Message);
    }
}

Then call it once from your host module:

public override void PreConfigureServices(ServiceConfigurationContext context)
{
    DynamicSortingGuard.Install();
}

That's the whole change. After deploying:

  • ?sorting=Name desc and any other plain property path (including nested ones like Tenant.Name) keep working
  • ?sorting=PasswordHash.Substring(0, 1) desc, ..., ?sorting=PasswordHash.StartsWith("A") desc, ?sorting=(PasswordHash > "X") desc and similar expression payloads now return HTTP 400 with a generic "Your request is not valid!" body — the specific reason is logged on the server, not echoed to the client, so an attacker can't probe what shape is allowed

A built-in version of this guard is going into the framework so future releases ship the same protection out of the box without any host-side code. Plain property sorting by sensitive columns (e.g. ?sorting=PasswordHash) is a separate hardening layer — the right place to address that is a DTO-property allow-list, and we're tracking it as a follow-up to this fix.

About the __tenant header pivot and the missing per-IP rate limit on these endpoints: the rate-limit policy on Account.SendPasswordResetCode does exist but partitions by email, so it doesn't apply here. We don't ship a built-in per-IP throttle for list endpoints because the right threshold depends on your deployment — ASP.NET Core's RateLimiter middleware scoped to /api/identity/* (or a rule at your reverse proxy / ingress) is the standard place for that.

Thanks

Hi,

The code matches your quote exactly in LeptonX Blazorise 5.4.0 / 5.4.1 / current dev — anonymous lambdas in ContentToolbar.OnInitializedAsync, no IDisposable. Your stack trace lines up — <<OnInitializedAsync>b__7_1>d.MoveNext() is the PropertyChanged lambda. The MudBlazor LeptonX ContentToolbar is already structured differently (implements IDisposable, named handlers, swallows ObjectDisposedException around the render call), so the fix shape on the Blazorise side is the same direction.

On reproduction: the race didn't fire in our local Blazor Web App + InteractiveAuto + Blazorise LeptonX setup. The code path is unambiguous regardless, and your SafeContentToolbar is on the right pattern.

Breadcrumbs in the same folder has the same bug — anonymous lambdas in OnInitializedAsync, no IDisposable. Two more LeptonX Blazorise menu components (ApplicationLayout/TopMenu/Navigation/MainMenuItem and PublicWebsiteLayout/Navigation/MainMenuItem) have a related leak: they wire PageLayout.PropertyChanged from OnParametersSet (so the handler re-adds on every render) and only unsubscribe NavigationManager.LocationChanged in Dispose. Slow leak rather than the crash you saw, but we'll cover them in the framework fix.

Before we land the framework change, could you try this Breadcrumbs override next to your SafeContentToolbar and let us know whether the prerender ObjectDisposedException and the related unobserved-task log noise stop showing up?

using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.Common;
using Volo.Abp.DependencyInjection;

namespace MyCompanyName.MyProjectName;

[ExposeServices(typeof(Breadcrumbs))]
[Dependency(ReplaceServices = true)]
public class SafeBreadcrumbs : Breadcrumbs, IDisposable
{
    private bool _disposed;
    private NotifyCollectionChangedEventHandler? _collectionChanged;
    private PropertyChangedEventHandler? _propertyChanged;

    protected override Task OnInitializedAsync()
    {
        // Skip base — its body wires the same unguarded lambdas this class replaces.
        if (!RendererInfo.IsInteractive)
        {
            return Task.CompletedTask;
        }

        _collectionChanged = async (_, _) =>
        {
            if (!_disposed)
            {
                await InvokeAsync(StateHasChanged);
            }
        };
        _propertyChanged = async (_, _) =>
        {
            if (!_disposed)
            {
                await InvokeAsync(StateHasChanged);
            }
        };

        PageLayout.BreadcrumbItems.CollectionChanged += _collectionChanged;
        PageLayout.PropertyChanged += _propertyChanged;
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _disposed = true;

        if (_collectionChanged is not null)
        {
            PageLayout.BreadcrumbItems.CollectionChanged -= _collectionChanged;
        }

        if (_propertyChanged is not null)
        {
            PageLayout.PropertyChanged -= _propertyChanged;
        }
    }
}

If running with SafeContentToolbar + SafeBreadcrumbs for a few days confirms the crash and the log noise go away on your side, we'll roll the same pattern into the LeptonX Blazorise components in the next release and cover the two MainMenuItem files at the same time.

Thanks

Hi,

The code matches your quote exactly in LeptonX Blazorise 5.4.0 / 5.4.1 / current dev — anonymous lambdas in ContentToolbar.OnInitializedAsync, no IDisposable. Your stack trace lines up — <<OnInitializedAsync>b__7_1>d.MoveNext() is the PropertyChanged lambda. The MudBlazor LeptonX ContentToolbar is already structured differently (implements IDisposable, named handlers, swallows ObjectDisposedException around the render call), so the fix shape on the Blazorise side is the same direction.

On reproduction: the race didn't fire in our local Blazor Web App + InteractiveAuto + Blazorise LeptonX setup. The code path is unambiguous regardless, and your SafeContentToolbar is on the right pattern.

Breadcrumbs in the same folder has the same bug — anonymous lambdas in OnInitializedAsync, no IDisposable. Two more LeptonX Blazorise menu components (ApplicationLayout/TopMenu/Navigation/MainMenuItem and PublicWebsiteLayout/Navigation/MainMenuItem) have a related leak: they wire PageLayout.PropertyChanged from OnParametersSet (so the handler re-adds on every render) and only unsubscribe NavigationManager.LocationChanged in Dispose. Slow leak rather than the crash you saw, but we'll cover them in the framework fix.

Before we land the framework change, could you try this Breadcrumbs override next to your SafeContentToolbar and let us know whether the prerender ObjectDisposedException and the related unobserved-task log noise stop showing up?

using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.Common;
using Volo.Abp.DependencyInjection;

namespace MyCompanyName.MyProjectName;

[ExposeServices(typeof(Breadcrumbs))]
[Dependency(ReplaceServices = true)]
public class SafeBreadcrumbs : Breadcrumbs, IDisposable
{
    private bool _disposed;
    private NotifyCollectionChangedEventHandler? _collectionChanged;
    private PropertyChangedEventHandler? _propertyChanged;

    protected override Task OnInitializedAsync()
    {
        // Skip base — its body wires the same unguarded lambdas this class replaces.
        if (!RendererInfo.IsInteractive)
        {
            return Task.CompletedTask;
        }

        _collectionChanged = async (_, _) =>
        {
            if (!_disposed)
            {
                await InvokeAsync(StateHasChanged);
            }
        };
        _propertyChanged = async (_, _) =>
        {
            if (!_disposed)
            {
                await InvokeAsync(StateHasChanged);
            }
        };

        PageLayout.BreadcrumbItems.CollectionChanged += _collectionChanged;
        PageLayout.PropertyChanged += _propertyChanged;
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _disposed = true;

        if (_collectionChanged is not null)
        {
            PageLayout.BreadcrumbItems.CollectionChanged -= _collectionChanged;
        }

        if (_propertyChanged is not null)
        {
            PageLayout.PropertyChanged -= _propertyChanged;
        }
    }
}

If running with SafeContentToolbar + SafeBreadcrumbs for a few days confirms the crash and the log noise go away on your side, we'll roll the same pattern into the LeptonX Blazorise components in the next release and cover the two MainMenuItem files at the same time.

Thanks

Hi,

Yes, you can install Blazorise 2.2.0 on top of ABP 10.4.1 at the NuGet level — Volo.Abp.BlazoriseUI 10.4.1 declares the Blazorise dependency as >= 2.0.4 with no upper bound, so NuGet won't complain about 2.2.0.

A few notes:

  • ABP 10.4.1 was built and tested against Blazorise 2.0.4 — that's the supported baseline.
  • The next ABP version (10.5) will move to Blazorise 2.1.3, not 2.2.0 yet. No ABP release ships on 2.2.0.
  • For the components ABP itself wraps in Volo.Abp.BlazoriseUI (DataGrid, Snackbar, MessageService, Modal, common components), the public API between 2.0.4 and 2.2.0 is unchanged, so the framework code keeps working on 2.2.0.
  • Blazorise 2.2.0 does have a few changes at lower levels: new abstract methods on ClassProvider / StyleProvider, an extra parameter on IJSTimePickerModule.Initialize, an intermediate base class for text inputs. These only affect you if you have your own custom theme provider, a class deriving from BaseTextInput, or a custom JS module implementation. If you don't, you won't notice them.

So if you need something that only exists in 2.2.0 (PivotGrid, OnScreenKeyboard, Gestures, Barcode, etc.), you can run with the version override. If you hit any UI or runtime issue on 2.2.0 that doesn't happen on 2.0.4, roll back to 2.0.4 (or 2.1.3 once you upgrade to ABP 10.5).

Thanks

Hi,

Two things going on here, and you need both to fully close the enumeration:

1. Turn the setting on — covers the "user not found" branch only

Abp.Account.PreventEmailEnumeration defaults to false. When you turn it on (Admin → Settings → Account → Prevent email enumeration), the "email not found" path silently returns 204 instead of throwing 403. So far so good.

But the setting only covers the user == null branch in AccountAppService.SendPasswordResetCodeAsync. When the user actually exists, the method goes on to call AccountEmailer.SendPasswordResetLinkAsync, and any exception there (SMTP failure, missing email template, an appName that isn't registered with AppUrlOptions, etc.) bubbles up as HTTP 500 — which is exactly the differential you're seeing. The setting alone won't fix that.

2. Override AccountAppService.SendPasswordResetCodeAsync to swallow everything

This is the change that actually makes the response uniform. Put this in your *.HttpApi.Host (or any host that exposes this route):

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.Account.Emailing;
using Volo.Abp.Account.PhoneNumber;
using Volo.Abp.BlobStoring;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Imaging;
using Volo.Abp.SettingManagement;
using Volo.Abp.Users;

namespace MyCompanyName.MyProjectName;

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IAccountAppService), typeof(AccountAppService))]
public class EnumerationProofAccountAppService : AccountAppService
{
    public EnumerationProofAccountAppService(
        IdentityUserManager userManager,
        IAccountEmailer accountEmailer,
        IAccountPhoneService phoneService,
        IIdentityRoleRepository roleRepository,
        IdentitySecurityLogManager identitySecurityLogManager,
        IBlobContainer<AccountProfilePictureContainer> accountProfilePictureContainer,
        ISettingManager settingManager,
        IOptions<IdentityOptions> identityOptions,
        IIdentitySecurityLogRepository securityLogRepository,
        IImageCompressor imageCompressor,
        IOptions<AbpProfilePictureOptions> profilePictureOptions,
        IApplicationInfoAccessor applicationInfoAccessor,
        IdentityUserTwoFactorChecker identityUserTwoFactorChecker,
        IDistributedCache<EmailConfirmationCodeCacheItem> emailConfirmationCodeCache,
        IdentityErrorDescriber identityErrorDescriber,
        IOptions<AbpRegisterEmailConfirmationCodeOptions> registerEmailConfirmationCodeOptions)
        : base(userManager, accountEmailer, phoneService, roleRepository,
            identitySecurityLogManager, accountProfilePictureContainer, settingManager, identityOptions,
            securityLogRepository, imageCompressor, profilePictureOptions, applicationInfoAccessor,
            identityUserTwoFactorChecker, emailConfirmationCodeCache, identityErrorDescriber,
            registerEmailConfirmationCodeOptions)
    {
    }

    public override async Task SendPasswordResetCodeAsync(SendPasswordResetCodeDto input)
    {
        try
        {
            await base.SendPasswordResetCodeAsync(input);
        }
        catch (Exception ex)
        {
            Logger.LogException(ex);
        }
    }
}

With the setting on and the override in place, every input — existing user, non-existent email, valid appName, invalid appName — returns the same 204 No Content with an empty body. That's what removes the oracle. The same code path is present in current 10.x, so this isn't fixed by upgrading.

3. About rate limiting

You're right that for enumeration this endpoint is effectively unlimited. There is a built-in policy in AbpAccountPublicApplicationModule (Account.SendPasswordResetCode, 10/hour and 30/day), but it partitions by email, so an attacker rotating through email candidates never hits it. That policy is meant to stop a single victim getting spammed, not to stop enumeration.

For enumeration, add a per-IP limit on top. ASP.NET Core's built-in RateLimiter middleware scoped to /api/account/send-password-reset-code, or a rule at your reverse proxy (YARP / nginx / ingress), both work. Anything that buckets by client IP is fine.

4. About the __tenant header

Switching tenant context via __tenant is by design for multi-tenant clients, so we can't drop it in the framework. But the override above runs inside the tenant context, so once it's in place the response is uniform regardless of which tenant the attacker pivots into. The remaining tenant-existence oracle (the /api/abp/multi-tenancy/tenants/by-name|by-id endpoints) is the same one you reported in #10725 — the workaround there closes it.

Thanks

Great 👍

Showing 371 to 380 of 12923 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on November 02, 2026, 06:56
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.