Safeguard
DevSecOps

A Feature Flag That Disables a Control Is a Control You Do Not Have

Added during an incident to skip a validation or bypass a limit, intended to be reverted that afternoon, and nothing reminds anyone. It lives in a system with weaker access control and no change record than your permission model.

Priya Mehta
DevSecOps Lead
6 min read

Every feature flag is a branch that exists in production. Both sides of it are live code, only one side is usually exercised, and the flag can be flipped by someone in a web console without a deploy, a review or a record in your change process.

Most flags are harmless and temporary in intent. The problem is the ones that are neither, and they accumulate because removing a flag is work nobody schedules.

This post is the security-relevant part of flag debt. For whoever owns the flag system, which is often nobody in particular.

The flags that matter

Not the ones toggling a button colour. Three categories carry real consequences.

Flags that disable a control. Added during an incident or a migration: skip the validation, bypass the rate limit for this customer, turn off the new authorisation check while we debug. Each was correct at the time and each is a control you no longer have if it is still off.

The pattern that makes these dangerous is that they are created under pressure, by someone who intends to revert them that afternoon, and there is no mechanism that reminds anyone.

Flags that gate access. A flag deciding whether a user sees an admin panel or an internal tool is an authorisation decision implemented in a system designed for product experiments. It has different defaults, different audit properties and different people with write access than your permission model does.

Flags with per-customer overrides. The most common shape in B2B products, and the one that produces surprises. A flag that is off globally and on for eleven specific accounts means eleven configurations you are not testing, and somebody enabled each of them for a reason nobody wrote down.

The off path rots

A flag that has been on for eight months has an off path that has not run in eight months. The code is still there, still compiled, still reachable if anyone flips the flag back.

It has not been tested, the schema may have moved underneath it, and the dependencies it calls may have changed. So the rollback everyone assumes they have, flip it back if there is a problem, is a path with unknown behaviour, and you will discover that during the incident where you need it.

This is the strongest argument for removing flags promptly rather than leaving them as a safety mechanism. After a few months the safety mechanism is a liability pretending to be an option.

Flag changes escape change management

You have branch protection, required review, CI and a deploy record. A flag flip has none of that, and it can change application behaviour as much as a release.

If you have documented a change management control for an audit, this is a gap in it. The reasonable position is not to put flags behind a pull request, which defeats the purpose, but to treat them as a change with commensurate evidence:

  • Every flip logged, with who, what, when, previous value, and for which scope. Most platforms record this; the question is whether it is retained long enough and whether anyone can retrieve it.
  • Higher-risk flags require a second approver. Most systems support per-flag permissions. Use them for the ones in the three categories above.
  • Flips of security-relevant flags alert. Not block, alert. Somebody should know when the authorisation bypass is turned on.

Give every flag an expiry

The structural fix, and it is the same shape as the fix for contractor access and risk acceptances: make the unsafe state require an action to persist rather than to end.

At creation, record an owner and a removal date. Then report on the ones past their date, weekly, to their owners. The list is embarrassing on first run and short after a few months.

For flags that are genuinely permanent, such as a licensing tier or a regional behaviour, mark them as configuration rather than flags. They are a different thing with a different lifecycle, and mixing them into the same list is why nobody trusts the report.

Audit yours

# From your flag platform's API, the questions to answer:
#   - How many flags exist, and how many were created over 90 days ago?
#   - How many have been at the same value for 90 days? (they are decisions, not flags)
#   - How many have per-customer overrides, and who requested each?
#   - Which flags are referenced in code that no longer exists?
#   - Which flags disable a validation, an authorisation check or a limit?

# The last one is a grep, and it is the highest-value query here:
grep -rEn "flag|feature|toggle" --include=*.go --include=*.ts --include=*.java src/ \
  | grep -iE "skip|bypass|disable|allow_all|unsafe|legacy_auth"

That grep finds the flags nobody would describe as security-relevant when asked, because the person who added them thought of them as temporary.

Clean up in the right order

  1. Flags referenced by no code. Pure deletion, zero risk, and usually a third of the list.
  2. Flags at 100 percent for months. Remove the flag and the off path. This is real work because it means deleting code, which is why it does not happen on its own.
  3. Flags at 0 percent for months. The feature was abandoned. Remove both the flag and the dead feature.
  4. Flags with overrides. Talk to whoever owns those accounts. Some overrides are permanent product decisions and belong in configuration.
  5. The security-relevant ones. Deliberately, with someone who understands what the control was.

The concession

Flags are genuinely valuable, and the discipline described here has a cost that falls on teams already shipping fast. A trunk-based team relies on flags to merge incomplete work, and telling them to remove flags promptly competes directly with the thing flags are for.

The proportionate version is to separate the categories. Short-lived release flags need an expiry and cleanup, and little else. Flags that disable controls or gate access need approval, logging and an alert, and there are usually fewer than ten of those. Applying the heavy process to all of them is how the process gets abandoned.

The implication

A flag that disables a security control is a control you do not have, held in a system with weaker access control and no change record than the one you built for permissions.

You probably have a few. The grep above will find them in about a minute, and the answer to "who can turn this on, and would anyone know" is usually the finding.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.