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:

JavaScript ui-only-flag-antipattern.tsx
// 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:

JavaScript ui-only-flag-backend.js
// 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/export aufrufen.
  • 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:

JavaScript flag-enforcement-pattern.js
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.

JavaScript flag-plus-permission.js
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:

BegriffWas es istWo hingehört
Visibility-FlagUI-Element sichtbar/nicht sichtbarFrontend
Access-Control-FlagUser darf Endpoint nutzen / nichtBackend (Pflicht) + Frontend (optional, für UX)
PermissionUser hat das Recht für die AktionBackend (zwingend)
Plan-LimitFeature ist im Subscription-Plan enthaltenBackend (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

JavaScript percentage-rollout.js
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

JavaScript allowlist-rollout.js
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)

JavaScript feature-flag-service.js
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:

  1. Erstellen (1 % Rollout).
  2. Skalieren (5 %, 25 %, 50 %, 100 %).
  3. Cleanup — Flag-Eval entfernen, Code-Pfad fest in den Main-Pfad.
  4. 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:

JavaScript kill-switch-pattern.js
// 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:

JavaScript tenant-flag-override.js
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

/ Weiter

Zurück zu Autorisierung & Access Control

Zur Übersicht