Skip to content
Back to skills

Dotnet Feature Flags

ASecurity

Reviews ASP.NET Core feature-flag usage via Microsoft.FeatureManagement — the rollout-specific extension of dotnet-dynamic-configuration's generic DB-backed settings model. Flags feature branching done with ad-hoc if/config checks instead of IFeatureManager, no targeting-filter support for percentage rollout or user/tenant allow-lists, flags left in code long after a rollout completed, and flag evaluation results not exposed to the Angular frontend consistently. Outputs findings with pilot-do...

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 6, 2026
developmenttypescriptgoangularapifrontendbackend

Works with

  • api

Security analysis

A100/100

Scanned September 6, 2026

npx -y skills add AgenticPawan/FullStack-Pilot --skill dotnet-feature-flags --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dotnet Feature Flags?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dotnet Feature Flags
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/agenticpawan-dotnet-feature-flags/badge)](https://www.skillsdirectory.com/skills/agenticpawan-dotnet-feature-flags)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dotnet-feature-flags
description: Reviews ASP.NET Core feature-flag usage via Microsoft.FeatureManagement — the rollout-specific extension of dotnet-dynamic-configuration's generic DB-backed settings model. Flags feature branching done with ad-hoc if/config checks instead of IFeatureManager, no targeting-filter support for percentage rollout or user/tenant allow-lists, flags left in code long after a rollout completed, and flag evaluation results not exposed to the Angular frontend consistently. Outputs findings with pilot-dotnet feature-flags standard IDs.
when_to_use: feature flag, IFeatureManager, Microsoft.FeatureManagement, percentage rollout, targeting filter, feature gate, stale feature flag, flag cleanup, canary release, A/B rollout, beta feature toggle
---

## Standard IDs

| ID | Severity | What it checks |
|----|----------|-----------------|
| FF-001 | P1 | Feature branching done with ad-hoc `if(config[...])` instead of `IFeatureManager` |
| FF-002 | P1 | No targeting-filter support for percentage rollout or user/tenant allow-lists |
| FF-003 | P2 | Feature flag left in code long after its rollout completed |
| FF-004 | P2 | Flag evaluation not exposed to the Angular frontend consistently |

Builds on `dotnet-dynamic-configuration`'s DB-backed settings model — a feature flag is a
specific *kind* of dynamic configuration value with its own evaluation semantics
(percentage/targeting), not just an on/off boolean read from the settings table.

---

## Check A — Ad-hoc config checks instead of IFeatureManager (FF-001)

### Detection

Grep for `_configuration["Features:X"] == "true"` or a hand-rolled boolean read used to
gate a code path, instead of `IFeatureManager.IsEnabledAsync(...)`. The hand-rolled
version has no targeting/percentage support and no consistent evaluation contract across
the codebase — every feature check reinvents its own on/off logic.

### BAD — ad-hoc config-driven branching

```csharp
public async Task<IActionResult> Checkout(CheckoutDto dto)
{
    if (_configuration["Features:NewCheckoutFlow"] == "true") // string comparison, no targeting
    {
        return await _newCheckoutService.ProcessAsync(dto);
    }
    return await _legacyCheckoutService.ProcessAsync(dto);
}
```

### GOOD — IFeatureManager, backed by the DB-backed configuration source

```csharp
builder.Services.AddFeatureManagement()
    .AddFeatureFilter<PercentageFilter>()
    .AddFeatureFilter<TargetingFilter>();

public async Task<IActionResult> Checkout(CheckoutDto dto, IFeatureManager featureManager)
{
    if (await featureManager.IsEnabledAsync("NewCheckoutFlow"))
        return await _newCheckoutService.ProcessAsync(dto);
    return await _legacyCheckoutService.ProcessAsync(dto);
}
```

---

## Check B — No targeting-filter support (FF-002)

### Detection

For a flag intended to roll out gradually (not just be a static on/off switch), check
whether `TargetingFilter`/`PercentageFilter` is actually configured with targeting
criteria (specific users, tenants, or a percentage of traffic), versus a flag that's
either 0% or 100% enabled everywhere with no gradual-rollout mechanism.

### BAD — flag is a blunt global switch with no rollout control

```json
{
  "FeatureManagement": {
    "NewCheckoutFlow": true
  }
}
```

### GOOD — percentage rollout + beta-tenant allow-list

```json
{
  "FeatureManagement": {
    "NewCheckoutFlow": {
      "EnabledFor": [
        { "Name": "Percentage", "Parameters": { "Value": 10 } },
        { "Name": "Targeting", "Parameters": { "Audience": {
          "Groups": [{ "Name": "BetaTenants", "RolloutPercentage": 100 }],
          "DefaultRolloutPercentage": 0
        }}}
      ]
    }
  }
}
```

```csharp
public class TenantTargetingContextAccessor : ITargetingContextAccessor
{
    private readonly ICurrentUserService _currentUser;

    public ValueTask<TargetingContext> GetContextAsync() =>
        new(new TargetingContext
        {
            UserId = _currentUser.UserId.ToString(),
            Groups = new[] { _currentUser.TenantGroup }
        });
}
```

---

## Check C — Stale flag never cleaned up (FF-003)

### Detection

Check whether a flag that has been at 100% rollout (or 0%, permanently killed) for an
extended period still has both code branches present, plus the flag definition itself.
Every stale flag is dead-code risk and cognitive overhead — the "old" branch usually
stops being tested once the rollout completes but stays in the codebase indefinitely.

### BAD — flag fully rolled out months ago, both branches still exist

```csharp
// NewCheckoutFlow has been at 100% for 6 months; legacy path is untested dead code
// that still gets *compiled* and *reviewed* in every unrelated PR that touches this file.
if (await featureManager.IsEnabledAsync("NewCheckoutFlow"))
    return await _newCheckoutService.ProcessAsync(dto);
return await _legacyCheckoutService.ProcessAsync(dto);
```

### GOOD — flag removed once rollout is confirmed complete, only the winning path remains

```csharp
// NewCheckoutFlow flag removed 2026-01-15 after 100% rollout confirmed stable for 30 days.
return await _newCheckoutService.ProcessAsync(dto);
```

Track flag age/rollout percentage in the same DB-backed settings admin surface
`dotnet-dynamic-configuration` CFG-005 recommends, and flag any feature at 100%/0% for
longer than a documented threshold (e.g., 60 days) for cleanup.

---

## Check D — Flag evaluation not exposed to the frontend consistently (FF-004)

### Detection

For a flag that also changes Angular UI behavior (not just backend logic), check whether
the frontend re-implements its own copy of the same targeting logic instead of querying a
single `/api/features` endpoint that evaluates via the same `IFeatureManager` the backend
uses — two independent evaluations of "is this user in the 10% rollout" can disagree.

### BAD — Angular re-derives its own flag logic

```typescript
// Angular guesses at rollout eligibility independently of the backend's IFeatureManager
const isBeta = localStorage.getItem('betaUser') === 'true';
```

### GOOD — one evaluation source, exposed via an endpoint

```csharp
[HttpGet("api/features")]
public async Task<IActionResult> GetFeatures(IFeatureManager featureManager)
{
    var flags = new Dictionary<string, bool>();
    await foreach (var name in featureManager.GetFeatureNamesAsync())
        flags[name] = await featureManager.IsEnabledAsync(name);
    return Ok(flags);
}
```

```typescript
// FeatureFlagService fetches once per session from the same evaluation the backend uses
this.flags = await this.http.get<Record<string, boolean>>('/api/features').toPromise();
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.