Activities of "maliming"

Hi,

Thanks for the detailed logs, they confirmed the root cause clearly.

The logs show that permissions with .RequireFeatures("TourModule.TourService") are correctly filtered (StateCheck.IsEnabled=False). The ones that were still showing had CheckerCount=0 — meaning they didn't have .RequireFeatures() at all. Glad you already fixed those on your end.

Regarding your question about EditionFeatureValueProvider returning null:

This is expected behavior. EditionFeatureValueProvider queries the AbpFeatureValues table for an explicit record with ProviderName='E' and ProviderKey='{editionId}'. If no record exists, it returns null.

When you open the Feature Management dialog for the "standard" edition, the UI shows the checkbox as unchecked — but that's just displaying the resolved default value ("false" from your code), not a value that was saved to the database. Since you never explicitly changed and saved the feature value for this edition, no record was written to AbpFeatureValues. That's why EditionFeatureValueProvider returns null and the chain falls through to DefaultValueFeatureValueProvider which returns "false".

The provider chain works like a cascading lookup:

  1. TenantFeatureValueProvider → looks for tenant-specific override in DB → no record → null
  2. EditionFeatureValueProvider → looks for edition-specific override in DB → no record → null
  3. DefaultValueFeatureValueProvider → returns the code-defined default "false" ← used

If you want the edition to have an explicit value, you'd need to actually change the toggle in the Feature Management UI for that edition and click Save — that creates a record in the database.

About ABP Suite not adding .RequireFeatures() automatically — we'll discuss this internally as a potential improvement.

You can now remove the diagnostic file from your project.

Thanks

Hi,

Sorry about that, I provided code targeting a newer ABP version. Here is the corrected version for ABP 9.2.0 — the PermissionAppService constructor in 9.2.0 only has 4 parameters:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Features;
using Volo.Abp.MultiTenancy;
using Volo.Abp.PermissionManagement;
using Volo.Abp.SimpleStateChecking;

namespace YourNamespace;

public class DiagnosticPermissionAppService : PermissionAppService
{
    private readonly IFeatureChecker _featureChecker;
    private readonly IFeatureDefinitionManager _featureDefinitionManager;

    public DiagnosticPermissionAppService(
        IPermissionManager permissionManager,
        IPermissionDefinitionManager permissionDefinitionManager,
        IOptions<PermissionManagementOptions> options,
        ISimpleStateCheckerManager<PermissionDefinition> simpleStateCheckerManager,
        IFeatureChecker featureChecker,
        IFeatureDefinitionManager featureDefinitionManager)
        : base(permissionManager, permissionDefinitionManager, options, simpleStateCheckerManager)
    {
        _featureChecker = featureChecker;
        _featureDefinitionManager = featureDefinitionManager;
    }

    public override async Task<GetPermissionListResultDto> GetAsync(
        string providerName, string providerKey)
    {
        Logger.LogWarning("[Diag-Perm] ══════ GetAsync START ══════");

        Logger.LogWarning(
            "[Diag-Perm] Context: providerName={ProviderName}, providerKey={ProviderKey}, " +
            "CurrentTenant.Id={TenantId}, MultiTenancySide={Side}",
            providerName, providerKey,
            CurrentTenant.Id?.ToString() ?? "(null/host)",
            CurrentTenant.GetMultiTenancySide());

        await DiagnoseFeatureAsync("TourModule.TourService");

        foreach (var group in await PermissionDefinitionManager.GetGroupsAsync())
        {
            var tourPerms = group.GetPermissionsWithChildren()
                .Where(p => p.Name.Contains("Tour", StringComparison.OrdinalIgnoreCase))
                .ToList();

            if (!tourPerms.Any())
            {
                continue;
            }

            Logger.LogWarning(
                "[Diag-Perm] Group '{GroupName}': {Count} Tour permissions found",
                group.Name, tourPerms.Count);

            foreach (var perm in tourPerms)
            {
                var checkerTypes = perm.StateCheckers
                    .Select(c => c.GetType().Name)
                    .ToList();

                var featureNames = perm.StateCheckers
                    .OfType<RequireFeaturesSimpleStateChecker<PermissionDefinition>>()
                    .SelectMany(c => c.FeatureNames)
                    .ToList();

                bool stateCheckResult;
                try
                {
                    stateCheckResult = await SimpleStateCheckerManager.IsEnabledAsync(perm);
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, "[Diag-Perm] StateChecker EXCEPTION for '{Name}'", perm.Name);
                    stateCheckResult = false;
                }

                Logger.LogWarning(
                    "[Diag-Perm]   Permission '{Name}': " +
                    "CheckerCount={CheckerCount}, CheckerTypes=[{Checkers}], " +
                    "RequiredFeatures=[{Features}], StateCheck.IsEnabled={IsEnabled}",
                    perm.Name,
                    perm.StateCheckers.Count,
                    checkerTypes.Any() ? string.Join(", ", checkerTypes) : "(none)",
                    featureNames.Any() ? string.Join(", ", featureNames) : "(none)",
                    stateCheckResult);
            }
        }

        var result = await base.GetAsync(providerName, providerKey);

        var visible = result.Groups
            .SelectMany(g => g.Permissions)
            .Where(p => p.Name.Contains("Tour", StringComparison.OrdinalIgnoreCase))
            .Select(p => p.Name)
            .ToList();

        Logger.LogWarning(
            "[Diag-Perm] Final output: {Count} Tour permissions visible: [{Names}]",
            visible.Count,
            visible.Any() ? string.Join(", ", visible) : "(none)");

        Logger.LogWarning("[Diag-Perm] ══════ GetAsync END ══════");

        return result;
    }

    private async Task DiagnoseFeatureAsync(string featureName)
    {
        try
        {
            var featureDef = await _featureDefinitionManager.GetOrNullAsync(featureName);
            if (featureDef == null)
            {
                Logger.LogWarning(
                    "[Diag-Perm] Feature '{Name}' definition NOT FOUND!", featureName);
                return;
            }

            Logger.LogWarning(
                "[Diag-Perm] Feature '{Name}': DefaultValue='{Default}', " +
                "IsAvailableToHost={IsAvailableToHost}, AllowedProviders=[{Providers}]",
                featureName,
                featureDef.DefaultValue ?? "(null)",
                featureDef.IsAvailableToHost,
                featureDef.AllowedProviders.Any()
                    ? string.Join(", ", featureDef.AllowedProviders)
                    : "(all)");

            var rawValue = await _featureChecker.GetOrNullAsync(featureName);
            var isEnabled = await _featureChecker.IsEnabledAsync(featureName);

            Logger.LogWarning(
                "[Diag-Perm] IFeatureChecker for '{Name}': raw='{RawValue}', enabled={IsEnabled}",
                featureName, rawValue ?? "(null)", isEnabled);
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "[Diag-Perm] Error checking feature '{Name}'", featureName);
        }
    }
}

public class DiagnosticFeatureChecker : FeatureChecker
{
    private readonly ICurrentTenant _currentTenant;
    private readonly ILogger<DiagnosticFeatureChecker> _logger;

    private const string DiagFilter = "Tour";

    public DiagnosticFeatureChecker(
        IOptions<AbpFeatureOptions> options,
        IServiceProvider serviceProvider,
        IFeatureDefinitionManager featureDefinitionManager,
        IFeatureValueProviderManager featureValueProviderManager,
        ICurrentTenant currentTenant,
        ILogger<DiagnosticFeatureChecker> logger)
        : base(options, serviceProvider, featureDefinitionManager, featureValueProviderManager)
    {
        _currentTenant = currentTenant;
        _logger = logger;
    }

    public override async Task<string?> GetOrNullAsync(string name)
    {
        if (!name.Contains(DiagFilter, StringComparison.OrdinalIgnoreCase))
        {
            return await base.GetOrNullAsync(name);
        }

        _logger.LogWarning(
            "[Diag-Feature] ── GetOrNullAsync('{Name}'), CurrentTenant.Id={TenantId} ──",
            name, _currentTenant.Id?.ToString() ?? "(null/host)");

        var featureDefinition = await FeatureDefinitionManager.GetOrNullAsync(name);
        if (featureDefinition == null)
        {
            _logger.LogWarning(
                "[Diag-Feature] Feature '{Name}' NOT FOUND → returning null", name);
            return null;
        }

        _logger.LogWarning(
            "[Diag-Feature] Feature '{Name}': DefaultValue='{Default}'",
            name, featureDefinition.DefaultValue ?? "(null)");

        var providers = FeatureValueProviderManager.ValueProviders.Reverse().ToList();

        if (featureDefinition.AllowedProviders.Any())
        {
            providers = providers
                .Where(p => featureDefinition.AllowedProviders.Contains(p.Name))
                .ToList();
        }

        _logger.LogWarning(
            "[Diag-Feature] Provider chain: [{Chain}]",
            string.Join(" → ", providers.Select(p => p.Name + "(" + p.GetType().Name + ")")));

        foreach (var provider in providers)
        {
            string? value;
            try
            {
                value = await provider.GetOrNullAsync(featureDefinition);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex,
                    "[Diag-Feature] Provider '{Provider}' threw exception!", provider.Name);
                continue;
            }

            if (value != null)
            {
                _logger.LogWarning(
                    "[Diag-Feature] Provider '{Provider}' ({Type}) → '{Value}' ← WINNER",
                    provider.Name, provider.GetType().Name, value);
                return value;
            }

            _logger.LogWarning(
                "[Diag-Feature] Provider '{Provider}' ({Type}) → null",
                provider.Name, provider.GetType().Name);
        }

        _logger.LogWarning("[Diag-Feature] All providers returned null");
        return null;
    }
}

Register both in your module's ConfigureServices:

context.Services.AddTransient<IPermissionAppService, DiagnosticPermissionAppService>();
context.Services.AddTransient<IFeatureChecker, DiagnosticFeatureChecker>();

After deploying, open the Permission Management dialog for the tenant and share the log output (search for [Diag-Perm] and [Diag-Feature]).

Thanks

Hi,

.RequireFeatures() on permissions should hide them from the Permission Management UI when the feature is disabled. The PermissionAppService.GetInternalAsync checks each permission's state checkers (including RequireFeaturesSimpleStateChecker) and filters out permissions whose required features are not enabled.

To figure out why this isn't working in your environment, I've prepared a diagnostic file with two service overrides. They add detailed logging so we can see exactly what's happening at each step.

Add this file to your Administration Service project:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Features;
using Volo.Abp.MultiTenancy;
using Volo.Abp.PermissionManagement;
using Volo.Abp.SimpleStateChecking;

namespace YourNamespace;

// ═══════════════════════════════════════════════════════════════════
// HOW TO USE:
//
// Add this file to your Administration Service project, then register
// in your module's ConfigureServices:
//
//   // 1. Permission Management diagnostics
//   context.Services.AddTransient<IPermissionAppService, DiagnosticPermissionAppService>();
//
//   // 2. Feature Checker diagnostics (logs every provider resolution)
//   context.Services.AddTransient<IFeatureChecker, DiagnosticFeatureChecker>();
//
// Then open the Permission Management dialog for a tenant and check
// the application logs for lines starting with "[Diag-Perm]" and "[Diag-Feature]".
//
// REMOVE THIS FILE after troubleshooting is done.
// ═══════════════════════════════════════════════════════════════════

// ─────────────────────────────────────────────────────────────────
// 1. DiagnosticPermissionAppService
//    Logs: context, StateCheckers on Tour permissions, final results
// ─────────────────────────────────────────────────────────────────
public class DiagnosticPermissionAppService : PermissionAppService
{
    private readonly IFeatureChecker _featureChecker;
    private readonly IFeatureDefinitionManager _featureDefinitionManager;

    public DiagnosticPermissionAppService(
        IPermissionManager permissionManager,
        IPermissionChecker permissionChecker,
        IPermissionDefinitionManager permissionDefinitionManager,
        IResourcePermissionManager resourcePermissionManager,
        IResourcePermissionGrantRepository resourcePermissionGrantRepository,
        IOptions<PermissionManagementOptions> options,
        ISimpleStateCheckerManager<PermissionDefinition> simpleStateCheckerManager,
        IFeatureChecker featureChecker,
        IFeatureDefinitionManager featureDefinitionManager)
        : base(permissionManager, permissionChecker, permissionDefinitionManager,
            resourcePermissionManager, resourcePermissionGrantRepository,
            options, simpleStateCheckerManager)
    {
        _featureChecker = featureChecker;
        _featureDefinitionManager = featureDefinitionManager;
    }

    protected override async Task<GetPermissionListResultDto> GetInternalAsync(
        string groupName, string providerName, string providerKey)
    {
        Logger.LogWarning(
            "[Diag-Perm] ══════ GetInternalAsync START ══════");

        // ── Step 1: Log request context ──
        Logger.LogWarning(
            "[Diag-Perm] Context: providerName={ProviderName}, providerKey={ProviderKey}, " +
            "CurrentTenant.Id={TenantId}, MultiTenancySide={Side}",
            providerName, providerKey,
            CurrentTenant.Id?.ToString() ?? "(null/host)",
            CurrentTenant.GetMultiTenancySide());

        // ── Step 2: Diagnose feature value directly ──
        await DiagnoseFeatureAsync("TourModule.TourService");

        // ── Step 3: Inspect Tour permission StateCheckers ──
        foreach (var group in await PermissionDefinitionManager.GetGroupsAsync())
        {
            var tourPerms = group.GetPermissionsWithChildren()
                .Where(p => p.Name.Contains("Tour", StringComparison.OrdinalIgnoreCase))
                .ToList();

            if (!tourPerms.Any())
            {
                continue;
            }

            Logger.LogWarning(
                "[Diag-Perm] Group '{GroupName}': {Count} Tour permissions found",
                group.Name, tourPerms.Count);

            foreach (var perm in tourPerms)
            {
                var checkerTypes = perm.StateCheckers
                    .Select(c => c.GetType().Name)
                    .ToList();

                var featureNames = perm.StateCheckers
                    .OfType<RequireFeaturesSimpleStateChecker<PermissionDefinition>>()
                    .SelectMany(c => c.FeatureNames)
                    .ToList();

                bool stateCheckResult;
                try
                {
                    stateCheckResult = await SimpleStateCheckerManager.IsEnabledAsync(perm);
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, "[Diag-Perm] StateChecker EXCEPTION for '{Name}'", perm.Name);
                    stateCheckResult = false;
                }

                Logger.LogWarning(
                    "[Diag-Perm]   Permission '{Name}': " +
                    "CheckerCount={CheckerCount}, CheckerTypes=[{Checkers}], " +
                    "RequiredFeatures=[{Features}], StateCheck.IsEnabled={IsEnabled}",
                    perm.Name,
                    perm.StateCheckers.Count,
                    checkerTypes.Any() ? string.Join(", ", checkerTypes) : "(none)",
                    featureNames.Any() ? string.Join(", ", featureNames) : "(none)",
                    stateCheckResult);
            }
        }

        // ── Step 4: Run original logic ──
        var result = await base.GetInternalAsync(groupName, providerName, providerKey);

        // ── Step 5: Log Tour permissions in final output ──
        var visible = result.Groups
            .SelectMany(g => g.Permissions)
            .Where(p => p.Name.Contains("Tour", StringComparison.OrdinalIgnoreCase))
            .Select(p => p.Name)
            .ToList();

        Logger.LogWarning(
            "[Diag-Perm] Final output: {Count} Tour permissions visible: [{Names}]",
            visible.Count,
            visible.Any() ? string.Join(", ", visible) : "(none)");

        Logger.LogWarning("[Diag-Perm] ══════ GetInternalAsync END ══════");

        return result;
    }

    private async Task DiagnoseFeatureAsync(string featureName)
    {
        try
        {
            var featureDef = await _featureDefinitionManager.GetOrNullAsync(featureName);
            if (featureDef == null)
            {
                Logger.LogWarning(
                    "[Diag-Perm] Feature '{Name}' definition NOT FOUND in FeatureDefinitionManager!",
                    featureName);
                return;
            }

            Logger.LogWarning(
                "[Diag-Perm] Feature '{Name}': DefaultValue='{Default}', " +
                "IsAvailableToHost={IsAvailableToHost}, AllowedProviders=[{Providers}]",
                featureName,
                featureDef.DefaultValue ?? "(null)",
                featureDef.IsAvailableToHost,
                featureDef.AllowedProviders.Any()
                    ? string.Join(", ", featureDef.AllowedProviders)
                    : "(all)");

            var rawValue = await _featureChecker.GetOrNullAsync(featureName);
            var isEnabled = await _featureChecker.IsEnabledAsync(featureName);

            Logger.LogWarning(
                "[Diag-Perm] IFeatureChecker.GetOrNullAsync('{Name}')='{RawValue}', " +
                "IsEnabledAsync={IsEnabled}",
                featureName,
                rawValue ?? "(null)",
                isEnabled);
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "[Diag-Perm] Error checking feature '{Name}'", featureName);
        }
    }
}

// ─────────────────────────────────────────────────────────────────
// 2. DiagnosticFeatureChecker
//    Logs every provider in the resolution chain so we can see
//    exactly which provider returns a value and what that value is.
// ─────────────────────────────────────────────────────────────────
public class DiagnosticFeatureChecker : FeatureChecker
{
    private readonly ICurrentTenant _currentTenant;
    private readonly ILogger<DiagnosticFeatureChecker> _logger;

    // Only log details for features containing this keyword to avoid noise
    private const string DiagFilter = "Tour";

    public DiagnosticFeatureChecker(
        IOptions<AbpFeatureOptions> options,
        IServiceProvider serviceProvider,
        IFeatureDefinitionManager featureDefinitionManager,
        IFeatureValueProviderManager featureValueProviderManager,
        ICurrentTenant currentTenant,
        ILogger<DiagnosticFeatureChecker> logger)
        : base(options, serviceProvider, featureDefinitionManager, featureValueProviderManager)
    {
        _currentTenant = currentTenant;
        _logger = logger;
    }

    public override async Task<string?> GetOrNullAsync(string name)
    {
        if (!name.Contains(DiagFilter, StringComparison.OrdinalIgnoreCase))
        {
            return await base.GetOrNullAsync(name);
        }

        _logger.LogWarning(
            "[Diag-Feature] ── GetOrNullAsync('{Name}') START, CurrentTenant.Id={TenantId} ──",
            name, _currentTenant.Id?.ToString() ?? "(null/host)");

        var featureDefinition = await FeatureDefinitionManager.GetOrNullAsync(name);
        if (featureDefinition == null)
        {
            _logger.LogWarning(
                "[Diag-Feature] Feature '{Name}' definition NOT FOUND → returning null",
                name);
            return null;
        }

        _logger.LogWarning(
            "[Diag-Feature] Feature '{Name}' found: DefaultValue='{Default}'",
            name, featureDefinition.DefaultValue ?? "(null)");

        var providers = FeatureValueProviderManager.ValueProviders.Reverse().ToList();

        if (featureDefinition.AllowedProviders.Any())
        {
            providers = providers
                .Where(p => featureDefinition.AllowedProviders.Contains(p.Name))
                .ToList();
            _logger.LogWarning(
                "[Diag-Feature] AllowedProviders filter active: [{Providers}]",
                string.Join(", ", featureDefinition.AllowedProviders));
        }

        _logger.LogWarning(
            "[Diag-Feature] Provider chain (in evaluation order): [{Chain}]",
            string.Join(" → ", providers.Select(p => p.Name + "(" + p.GetType().Name + ")")));

        // Manually iterate providers to log each step
        foreach (var provider in providers)
        {
            string? value;
            try
            {
                value = await provider.GetOrNullAsync(featureDefinition);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex,
                    "[Diag-Feature] Provider '{Provider}' threw exception!", provider.Name);
                continue;
            }

            if (value != null)
            {
                _logger.LogWarning(
                    "[Diag-Feature] Provider '{Provider}' ({Type}) returned '{Value}' ← WINNER",
                    provider.Name, provider.GetType().Name, value);
                return value;
            }

            _logger.LogWarning(
                "[Diag-Feature] Provider '{Provider}' ({Type}) returned null → next",
                provider.Name, provider.GetType().Name);
        }

        _logger.LogWarning(
            "[Diag-Feature] All providers returned null → returning null");
        return null;
    }
}

Register in your module's ConfigureServices:

context.Services.AddTransient<IPermissionAppService, DiagnosticPermissionAppService>();
context.Services.AddTransient<IFeatureChecker, DiagnosticFeatureChecker>();

After deploying, open the Permission Management dialog for the tenant where TourServiceModule is disabled. Then search your logs for [Diag-Perm] and [Diag-Feature].

The logs will tell us:

  1. Whether the TourModule.TourService feature definition exists in the Administration Service
  2. What value IFeatureChecker returns and which provider (Tenant/Edition/Config/Default) provided it
  3. Whether Tour permissions have RequireFeaturesSimpleStateChecker in their StateCheckers list
  4. Whether StateCheck.IsEnabled returns true or false for each Tour permission
  5. Which Tour permissions end up in the final output

Please share the log output and we'll identify the root cause.

Thanks

Hi,

  • TokenCleanupBackgroundWorker — runs every 1 hour (cleans up expired OpenIddict tokens older than 14 days)
  • IdentitySessionCleanupBackgroundWorker — runs every 1 hour (cleans up inactive sessions older than 30 days)
  • ExcelFileCleanupWorker — already runs daily at 23:00
  • ExpiredAuditLogDeleterWorker — already runs daily at 23:00

So only the first 2 are running hourly. Changing them to daily is totally fine — they're cleaning up data that's 14-30 days old, so a few hours delay doesn't matter at all.

You're right that the Hangfire dashboard is read-only for recurring jobs. You need to configure them in your code:

Configure<TokenCleanupOptions>(options =>
{
    options.CleanupPeriod = (int)TimeSpan.FromDays(1).TotalMilliseconds;
});

Configure<IdentitySessionCleanupOptions>(options =>
{
    options.CleanupPeriod = (int)TimeSpan.FromDays(1).TotalMilliseconds;
});

However, I should mention that these 4 workers are very unlikely to be the cause of your socket growth issue. Even at hourly frequency, they only create a few database connections per run. That's nothing compared to the ~120 sockets/minute growth you're seeing.

The socket issue is more likely caused by something else in your application — maybe manually created SqlConnection or HttpClient instances that aren't being disposed, or Azure SNAT port exhaustion. I'd recommend using dotnet-counters to monitor what's actually consuming the sockets:

dotnet-counters monitor --process-id <PID> --counters "Microsoft.Data.SqlClient.EventSource,System.Net.Sockets,System.Net.Http"

Thanks

Hi,

Yes, the Scriban vulnerability has been fixed. We addressed this in two steps:

  • Upgraded Scriban to 6.6.0 to fix the NU1902 security warning: https://github.com/abpframework/abp/pull/25122 (related issue: https://github.com/abpframework/abp/issues/25121)
  • Upgraded Scriban to 7.0.0 to fix additional security vulnerabilities: https://github.com/abpframework/abp/pull/25170 (related issue: https://github.com/abpframework/abp/issues/25172)

Both fixes are included in version 10.2.0 and later.

Thanks

Thanks, we will check your new question.

Hi,

Thanks for sharing the error. Yes, Scriban 7.0.0 changed ParserOptions from struct to record, so overriding the version doesn't work with ABP 8.2.x.

The good news is ABP's Scriban usage does not trigger this vulnerability:

  • ABP creates a new TemplateContext instance for every render call — it never reuses TemplateContext across renders.
  • ABP does not configure MemberFilter or MemberRenamer on TemplateContext at all.

Since the vulnerability requires reusing a TemplateContext with dynamically tightened member access restrictions, and ABP doesn't do that, the actual risk is very low.

You can suppress this specific advisory warning by adding the following to your project file:

<ItemGroup>
  <NuGetAuditSuppress Include="https://github.com/advisories/GHSA-5wr9-m6jw-xx44" />
</ItemGroup>

Thanks

Hi,

The performance issue is resolved. This feature filtering question is a different topic — please create a new support question so we can track it separately.

Based on the code, ABP checks feature values in this order: Tenant (T) → Edition (E) → Configuration (C) → Default (D). The first non-null value wins. Your feature has defaultValue: "false", so if no other provider returns a value, the feature should be disabled and permissions should not show.

Since the permissions are still showing, some provider must be returning a value that enables the feature. To find out which one, please run this SQL on your SaaS/Administration service database (in microservices, AbpFeatureValues is typically NOT in the tenant database):

SELECT * FROM AbpFeatureValues 
WHERE Name LIKE '%TourModule%' OR Name LIKE '%TourService%'
ORDER BY ProviderName, ProviderKey;

Please share the result in the new question, along with:

  • Whether the tenant is assigned to an Edition
  • The feature definition and permission definition code you mentioned earlier

Thanks

Hi,

The performance issue is resolved now. The feature filtering question is a separate topic — could you please create a new support question for it?

To help us investigate quickly, please include the following in the new question:

  1. The full feature definition code (TMSMSFeature.cs and your FeatureDefinitionProvider.cs)
  2. A few permission definitions from TourServicePermissionDefinitionProvider.cs showing the .RequireFeatures() usage
  3. Screenshot of the Feature Management UI with TourModule disabled — and please note whether you're viewing it as Host or as a specific Tenant
  4. The feature values from the database:
SELECT * FROM AbpFeatureValues 
WHERE Name LIKE '%TourModule%' OR Name LIKE '%TourService%'
ORDER BY ProviderName, ProviderKey;
  1. Which ABP version you're using for this environment

Thanks

Hi,

The CWE-693 vulnerability (GHSA-5wr9-m6jw-xx44) in Scriban is about TemplateContext caching type member accessors by Type only. It can be exploited when a TemplateContext instance is reused across renders and the MemberFilter or MemberRenamer is tightened between renders — previously cached accessors may still be exposed.

However, ABP's Scriban usage does not trigger this vulnerability:

  • ABP creates a new TemplateContext instance for every render call — it never reuses TemplateContext across renders.
  • ABP does not configure MemberFilter or MemberRenamer on TemplateContext at all.

Since the vulnerability requires reusing a TemplateContext with dynamically tightened member access restrictions, and ABP doesn't do that, the actual risk is very low.

That said, if you want to eliminate the warning entirely, you can override the Scriban version to 7.0.0 in your project file. We tested this with ABP 8.2.3 on .NET 8 and it works without any issues:

<ItemGroup>
  <PackageReference Include="Scriban" Version="7.0.0" />
</ItemGroup>

This will resolve all Scriban-related security warnings.

Alternatively, if you prefer to keep the original version, you can suppress specific advisories:

<ItemGroup>
  <NuGetAuditSuppress Include="https://github.com/advisories/GHSA-5wr9-m6jw-xx44" />
</ItemGroup>

Thanks

Showing 621 to 630 of 12928 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.