Feature-Flags sind ein verbreitetes Pattern für schrittweise Rollouts, A/B-Tests und Kill-Switches. Sicherheits-relevant werden sie, wenn Teams sie als Access-Control-Mechanismus missbrauchen: „Diese Funktion ist nur für Beta-Tester verfügbar — wir zeigen den Button nur denen." Wenn der Backend-Endpoint dabei keine eigene Permission-Prüfung hat, ist der Flag eine reine UI-Convention — und jede:r kann den Endpoint direkt aufrufen.
Was Feature-Flags sind — und was sie nicht sind
Feature-Flags (auch Feature-Toggles) sind bedingte Schalter im Code, die Features pro User, pro Gruppe, pro Prozent-Rollout oder pro Umgebung aktivieren/deaktivieren.
Legitime Use-Cases:
- Schrittweise Rollouts — neues Feature für 1 %, dann 10 %, dann alle.
- A/B-Tests — zwei Varianten des Checkout-Flow.
- Kill-Switches — bei Problemen Feature sofort abdrehen ohne Deploy.
- Feature-Branches — Code im Main-Branch, aber per Flag deaktiviert.
- Berechtigungs-Differenzierung zwischen Free und Premium Plans.
Was sie nicht sind:
- Access-Control für sensitive Funktionen.
- Permission-System für Rollen-basierte Authorization.
- Sicherheits-Boundary zwischen Tenants oder User-Klassen.
Das Antipattern — UI-only Flag
Beispiel:
// Frontend
function AdminPanel() {
const { user } = useAuth();
const showBetaFeature = user.flags.includes('beta-billing');
return (
<div>
{/* Standard-Buttons */}
<Button onClick={normalAction}>Standard</Button>
{/* Beta-Feature nur für Flag-User sichtbar */}
{showBetaFeature && (
<Button onClick={() => fetch('/api/beta/billing/export')}>
Export Billing (Beta)
</Button>
)}
</div>
);
}Backend:
// Schadhaft — keine eigene Prüfung des Flags
app.get('/api/beta/billing/export', requireAuth, async (req, res) => {
// Endpoint ist „beta" — wir gehen davon aus, dass das Frontend nur Beta-User
// hierhin schickt
const data = await db.billing.findAll(); // alle billing-Daten zurück
res.json(data);
});Was schief geht:
- Jeder eingeloggte User kann
curl https://api.example.com/api/beta/billing/exportaufrufen. - Endpoint ist nicht im UI verlinkt — aber erreichbar.
- Sensitive Daten leak ohne Beta-Flag-Check.
Faustregel: Wenn das Frontend einen Permission-Check macht, muss das Backend denselben Check machen — strikter.
Serverseitige Flag-Enforcement
Korrekte Implementierung:
function requireFlag(flagName) {
return (req, res, next) => {
if (!req.user.flags.includes(flagName)) {
return res.status(404).end(); // 404 statt 403 (Endpoint-Hiding)
}
next();
};
}
// Endpoint mit Flag-Check
app.get('/api/beta/billing/export',
requireAuth,
requireFlag('beta-billing'),
async (req, res) => {
const data = await db.billing.findAll({ tenantId: req.user.tenantId });
res.json(data);
}
);Plus die requirePermission-Schicht für die tatsächliche Berechtigung — Flag ist Rollout-Steuerung, Permission ist AuthZ.
app.get('/api/beta/billing/export',
requireAuth,
requireFlag('beta-billing'), // Feature-Rollout-Schicht
requirePermission('billing:read'), // AuthZ-Schicht
handler
);Zwei getrennte Schichten: Flag entscheidet, ob das Feature überhaupt aktiv ist; Permission entscheidet, ob der User das darf.
Visibility-Flag vs. Access-Control-Flag
Eine klare Begriffs-Trennung hilft:
| Begriff | Was es ist | Wo hingehört |
|---|---|---|
| Visibility-Flag | UI-Element sichtbar/nicht sichtbar | Frontend |
| Access-Control-Flag | User darf Endpoint nutzen / nicht | Backend (Pflicht) + Frontend (optional, für UX) |
| Permission | User hat das Recht für die Aktion | Backend (zwingend) |
| Plan-Limit | Feature ist im Subscription-Plan enthalten | Backend (zwingend) + UI-Hinweise |
Beispiel-Konstellation:
- Visibility-Flag „dark-mode" — Sichtbarkeit des Toggle-Buttons. Sicherheits-irrelevant.
- Access-Control-Flag „beta-api-v2" — neuer API-Endpoint für Beta-Tester. Server muss prüfen.
- Permission „billing:read" — User darf Billing sehen. Server muss prüfen.
- Plan-Limit „premium-features" — User hat Premium-Abo. Server muss prüfen.
Soft-Launch-Strategien
Soft-Launch ist die Phase, in der ein Feature schrittweise an User verteilt wird — Beobachtung von Fehlerraten, Performance, User-Feedback vor vollständigem Rollout.
Pattern 1: Prozentualer Rollout
function isInRollout(userId, percentage) {
// Deterministischer Hash — User landet immer im selben Bucket
const hash = crypto.createHash('sha256').update(`feature-x:${userId}`).digest();
const bucket = hash.readUInt32BE(0) % 100;
return bucket < percentage;
}
// 10 % Rollout
if (isInRollout(req.user.id, 10)) {
// Neuer Code
} else {
// Alter Code
}Vorteil: deterministisch. Wenn man von 10 % auf 20 % erhöht, bleiben die ersten 10 % drin (kein Hin-und-Her).
Pattern 2: Allow-list nach User-Gruppen
const BETA_TESTERS = new Set(['user-42', 'user-99', 'user-101']);
const BETA_TENANTS = new Set(['acme', 'globex']);
function inBeta(user) {
return BETA_TESTERS.has(user.id) || BETA_TENANTS.has(user.tenant);
}Pattern 3: Feature-Flag-Service (LaunchDarkly, GrowthBook, Unleash)
import { GrowthBook } from '@growthbook/growthbook';
const gb = new GrowthBook({
apiHost: 'https://cdn.growthbook.io',
clientKey: 'sdk-xxx',
});
// Server-side Flag-Eval mit User-Kontext
gb.setAttributes({
id: req.user.id,
tenant: req.user.tenant,
plan: req.user.plan,
});
if (gb.isOn('new-checkout-flow')) {
// ...
}Vorteil eines Services:
- Hot-Reload — Flag-Änderungen ohne Deploy.
- Audit-Log — wer hat wann was geändert.
- Targeting-Rules — komplex (Tenant + Plan + Region + Cohort).
- A/B-Testing-Integration.
Was zu beachten:
- Service-Latenz — Flag-Eval bei jedem Request darf nicht Bottleneck sein. Lösung: lokale Cache, periodischer Refresh.
- Service-Ausfall — Default-Verhalten bei Flag-Service-Outage muss definiert sein (Default-Allow oder Default-Deny pro Flag).
- PII in Targeting-Rules — User-IDs/E-Mails als Targeting-Attribute leaken zum Service.
Flag-Lebenszyklus
Feature-Flags sind temporäre Konstrukte. Wenn ein Feature komplett ausgerollt ist, sollte der Flag entfernt werden — sonst sammelt sich „Flag-Schulden" an.
Lebenszyklus:
- Erstellen (1 % Rollout).
- Skalieren (5 %, 25 %, 50 %, 100 %).
- Cleanup — Flag-Eval entfernen, Code-Pfad fest in den Main-Pfad.
- Löschen im Flag-Service.
Anti-Pattern: Flags, die seit 2 Jahren auf 100 % stehen und niemand entfernt — kosten DB-Lookups, machen Code unleserlich, schaffen tote Code-Pfade.
Tooling:
- LaunchDarkly Code Refs — markiert Flags ohne Code-Referenz.
- GrowthBook stale-Flag-Detection.
- Custom-Linter für lange-existierende Flags.
Best Practice: pro Flag bei Erstellung Expiry-Datum mitgeben — nach Ablauf Cleanup-Task im Backlog.
Sicherheits-relevante Flag-Patterns
Kill-Switch für Notfälle:
// Globaler Kill-Switch
app.post('/api/transactions', async (req, res) => {
if (await isFlagOn('transactions:disabled')) {
return res.status(503).json({ error: 'Service temporarily unavailable' });
}
// ... normal handler ...
});Wenn eine Sicherheits-Lücke entdeckt wird, kann das Feature in Minuten abgeschaltet werden — ohne Code-Deploy.
Gradual Rollout für sicherheitsrelevante Changes:
Algorithmus-Migration (z. B. bcrypt → argon2id) oder Permission-System-Wechsel sollten mit Flag gradual ausgerollt werden — Rollback im Fehlerfall sofort möglich.
Per-Tenant-Flag-Override für Enterprise-Customer:
async function isFeatureOn(featureName, user) {
// 1. Tenant-spezifischer Override?
const tenantFlag = await db.tenantFlags.findOne({
tenant: user.tenant, name: featureName
});
if (tenantFlag) return tenantFlag.enabled;
// 2. Globaler Rollout
return gb.isOn(featureName, { userId: user.id });
}Audit und Verantwortlichkeit
Feature-Flags ändern Verhalten in Produktion ohne Code-Deploy. Das ist mächtig — und potentiell gefährlich, wenn nicht kontrolliert.
Audit-Anforderungen:
- Wer hat wann welchen Flag geändert? Logging im Flag-Service.
- Change-Review — kritische Flags sollten Approval brauchen (Two-Person-Rule für Kill-Switches).
- Rollback-Möglichkeit — schnell zurücksetzbar.
- Documentation — pro Flag: Zweck, Owner, geplantes Cleanup-Datum.
Compliance-Aspekt:
Für regulierte Industries (Finance, Healthcare) sind Flag-Änderungen Code-Änderungen-äquivalent — sie müssen den gleichen Approval-Prozess durchlaufen. Naive „Marketing kann live Flags umschalten" passt nicht.
Interessantes
Feature-Flag-Service als Single Point of Failure
Wenn jeder Request Flag-Eval gegen externen Service macht, ist der Service ein SPOF. Lokales Caching (TTL ~1 min) plus Default-Werte bei Outage sind Pflicht. GrowthBook, LaunchDarkly, Unleash haben alle SDKs mit lokalem Cache.
Flag-Targeting-Rules als versteckte Permission-Engine
Wenn Flag-Targeting komplex wird (Tenant + Region + Plan + Cohort), ist es de-facto eine Permission-Engine — nur ohne Audit/Testing/Code-Review. Lösung: für AuthZ eine echte Policy-Engine (siehe rbac-und-abac), Flags nur für Rollout-Schicht.
PII in Flag-Targeting leakt zum Service
Wenn ein Flag-Service User-IDs, E-Mails, Plan-Daten als Targeting-Attribute bekommt, leaken diese Daten zum 3rd-Party-Provider. DSGVO-Implikationen, Service-Provider-Vertrag prüfen. Alternative: gehashte/pseudonymisierte IDs als Targeting-Attribute.
Test in Production durch Flag-Mock
Wer Features pro User-Header (X-Mock-Flag: feature-x=on) testen kann, hat Convenience — aber wenn der Header in Production nicht gefiltert ist, hat jede:r Zugriff auf alle Features. Header-Override nur in Dev/Staging zulassen, in Production strict 403.
Flag-Wert in Sessions cachen — aber wann invalidieren?
Wenn der Flag-Wert in der Session steht (Performance-Optimierung), wirkt eine Flag-Änderung erst nach Login/Session-Refresh. Bei sicherheits-relevanten Flags (Kill-Switch) gefährlich. Pattern: kritische Flags per-Request evaluieren, niemals cachen.
A/B-Test-Bias durch ungleiche Permission-Sets
Wenn die A-Variante nur „normale User" testet und die B-Variante zufällig nur Premium-User (durch Targeting-Rule), sind die Test-Resultate verzerrt. Sauber: A/B-Targeting orthogonal zu User-Segments definieren.
Stale Flags als Code-Smell
Flag, der seit 6 Monaten auf 100 % steht — Code-Pfad ist tot, Flag-Service-Lookup ist Overhead, andere Devs sind verwirrt. Quartal-Audit aller Flags, Cleanup-Tickets im Backlog. Tools wie LaunchDarkly Code Refs finden stale Flags automatisch.
Weiterführende Ressourcen
Externe Quellen
- Martin Fowler — Feature Toggles
- GrowthBook Documentation
- LaunchDarkly Documentation
- Unleash (Open Source)
- OWASP API Top 10 — BFLA
- OWASP Authorization Cheat Sheet