LaunchDarkly Hardening Guide
Feature flag security for SDK keys, environment access, and approval workflows
Overview
LaunchDarkly manages feature flags controlling application behavior across environments. REST API, SDK keys, and webhook integrations control feature rollouts. Compromised access enables feature manipulation, environment privilege escalation, or extraction of targeting rules revealing business logic.
Intended Audience
- Security engineers managing feature flag systems
- DevOps/Platform administrators
- GRC professionals assessing release management
- Third-party risk managers evaluating deployment integrations
How to Use This Guide
- L1 (Crawl): Essential controls for all organizations
- L2 (Walk): Enhanced controls for security-sensitive environments
- L3 (Run): Strictest controls for regulated industries
Scope
This guide covers LaunchDarkly security configurations including authentication, access controls, and integration security.
Table of Contents
1. Authentication & Access Controls
1.1 Enforce SSO with MFA
Profile Level: L1 (Crawl) NIST 800-53: IA-2(1)
Description
Require SAML single sign-on with multi-factor authentication for all LaunchDarkly account access, and provision and deprovision users automatically through SCIM.
Rationale
Why This Matters:
- Centralizes LaunchDarkly authentication in your corporate IdP so MFA and conditional-access policies apply to every login
- Local or password-only logins bypass IdP controls and are prime targets for credential stuffing and phishing
- SCIM provisioning removes departed users automatically, eliminating orphaned accounts that retain flag-modification rights
- A single compromised LaunchDarkly login can flip feature flags in production, exposing hidden features or disabling security controls
Attack Prevented: Credential theft, phishing, MFA bypass, orphaned-account access
ClickOps Implementation
Step 1: Configure SAML SSO
- Navigate to: gear icon (left sidebar) → Organization settings → Security → SSO Management
- Click Configure SAML and enter your IdP’s SSO URL, X.509 certificate, and (if required) the request signing settings
- Enable: Require SSO
Set the default initial role to “No access.” LaunchDarkly recommends configuring the default initial role for SSO-provisioned members to the lowest privilege available so that a newly federated account cannot read or change flags before an admin grants scoped access. On Developer and Foundation plans the lowest available default is Reader; Enterprise plans can set No access. (SSO documentation)
One identity provider per account. LaunchDarkly supports a single configured identity provider per account — you cannot federate a second IdP alongside the first, so plan IdP migrations as a cutover with an admin fallback path. (SSO documentation)
Step 2: Configure SCIM
- Enable SCIM provisioning (Enterprise — see Appendix A)
- Configure user/group sync
- Set deprovisioning behavior
Code Pack: API Script
# Audit all members for MFA enrollment
MEMBERS=$(ld_get "/members?limit=100") || {
fail "1.1 Unable to retrieve member list"
increment_failed; summary; exit 0
}
TOTAL=$(echo "${MEMBERS}" | jq '.totalCount')
NO_MFA=$(echo "${MEMBERS}" | jq '[.items[] | select(.mfa == false)] | length')
info "1.1 Total members: ${TOTAL}, without MFA: ${NO_MFA}"
if [ "${NO_MFA}" -gt 0 ]; then
warn "1.1 Members without MFA:"
echo "${MEMBERS}" | jq -r '.items[] | select(.mfa == false) | " - \(.email) (role: \(.role))"'
fail "1.1 ${NO_MFA} member(s) do not have MFA enabled"
increment_failed
else
pass "1.1 All members have MFA enabled"
increment_applied
fi
1.2 Role-Based Access Control
Profile Level: L1 (Crawl) NIST 800-53: AC-3, AC-6
Description
Define custom roles and project- and environment-scoped permissions so each member has only the LaunchDarkly access their job requires.
Rationale
Why This Matters:
- Least-privilege roles limit who can create, modify, or toggle flags in sensitive environments like production
- Default broad access lets any member change targeting rules that govern application behavior and customer exposure
- Scoping roles to specific projects and environments contains the blast radius of a single compromised account
- Reader-only roles for auditors and observers prevent accidental or malicious flag changes
Attack Prevented: Privilege escalation, unauthorized flag changes, lateral movement across projects, insider misuse
ClickOps Implementation
Step 1: Define Custom Roles
| Role | Permissions |
|---|---|
| Admin | Full access |
| Writer | Create/modify flags |
| Reader | View only |
| No access | Blocked |
Step 2: Configure Project/Environment Access
- Navigate to: gear icon (left sidebar) → Organization settings → Roles, then click New role
- Create environment-specific roles
- Apply least privilege
Custom roles are an Enterprise capability — see Appendix A. (Creating custom roles)
Code Pack: Terraform
# Production read-only role — least-privilege RBAC
resource "launchdarkly_custom_role" "prod_readonly" {
key = "hth-prod-readonly"
name = "HTH Production Read-Only"
description = "View production flags without modification rights"
base_permissions = "no_access"
policy_statements {
effect = "allow"
actions = ["viewProject"]
resources = ["proj/*"]
}
policy_statements {
effect = "deny"
actions = ["updateOn", "updateOff", "updateRules", "updateTargets", "updateFallthrough"]
resources = ["proj/*:env/production:flag/*"]
}
}
# Staging deployer role — write flags in staging only
resource "launchdarkly_custom_role" "staging_deployer" {
key = "hth-staging-deployer"
name = "HTH Staging Deployer"
description = "Manage flags in staging, read-only in production"
base_permissions = "no_access"
policy_statements {
effect = "allow"
actions = ["viewProject"]
resources = ["proj/*"]
}
policy_statements {
effect = "allow"
actions = ["*"]
resources = ["proj/*:env/staging:flag/*"]
}
policy_statements {
effect = "deny"
actions = ["*"]
resources = ["proj/*:env/production:flag/*"]
}
}
# Scoped service token for CI/CD
resource "launchdarkly_access_token" "cicd" {
name = "HTH CI/CD Pipeline"
service_token = true
inline_roles {
effect = "allow"
actions = ["updateOn", "updateOff"]
resources = ["proj/${var.project_key}:env/staging:flag/*"]
}
inline_roles {
effect = "deny"
actions = ["*"]
resources = ["proj/${var.project_key}:env/production:flag/*"]
}
}
Code Pack: API Script
# Create a production read-only custom role
EXISTING=$(ld_get "/roles" | jq -r '.items[].key') || true
if echo "${EXISTING}" | grep -q "^hth-prod-readonly$"; then
info "1.2 Role 'hth-prod-readonly' already exists"
else
info "1.2 Creating 'hth-prod-readonly' custom role..."
RESPONSE=$(ld_post "/roles" '{
"key": "hth-prod-readonly",
"name": "HTH Production Read-Only",
"description": "Read-only access to production environment",
"basePermissions": "no_access",
"policy": [
{
"effect": "allow",
"actions": ["viewProject"],
"resources": ["proj/*"]
},
{
"effect": "deny",
"actions": ["updateOn", "updateOff", "updateRules", "updateTargets", "updateFallthrough"],
"resources": ["proj/*:env/production:flag/*"]
}
]
}') || {
fail "1.2 Failed to create custom role"
increment_failed; summary; exit 0
}
pass "1.2 Custom role 'hth-prod-readonly' created"
fi
Code Pack: Sigma Detection Rule
detection:
selection:
kind: 'member'
accesses.action: 'updateMemberRole'
filter_admin:
title|contains: 'admin'
condition: selection and filter_admin
fields:
- date
- title
- member.email
- subject.name
- accesses.resource
2. SDK & API Security
2.1 Secure SDK Keys
Profile Level: L1 (Crawl) NIST 800-53: IA-5
Description
Protect LaunchDarkly SDK keys.
Rationale
Why This Matters:
- Server-side SDK keys grant read access to all flags and targeting rules in an environment and must never ship in client code
- Mobile keys and client-side IDs are exposure-safe, but using the wrong key type leaks server-only data to browsers and mobile apps
- Periodic key rotation limits how long a leaked key remains usable by an attacker
- Targeting rules often encode business logic, customer segments, and rollout plans that competitors or attackers can exploit
Attack Prevented: SDK key leakage, flag enumeration, targeting-rule extraction, business-logic disclosure
Attack Scenario: Exposed SDK key enables flag enumeration; mobile SDK key in client bundle allows targeting rule extraction.
Implementation
SDK Key Types:
| Key Type | Exposure Risk | Use Case |
|---|---|---|
| SDK Key | Server-side only | Backend services |
| Mobile Key | Client-side safe | Mobile apps |
| Client-side ID | Client-side safe | Browser apps |
Step 1: Rotate Keys
- Navigate to: Project settings → Environments
- Reset SDK keys periodically
- Update applications
Code Pack: API Script
# Check secure mode on all environments
ENVIRONMENTS=$(ld_get "/projects/${LD_PROJECT_KEY}/environments") || {
fail "2.1 Unable to retrieve environments"
increment_failed; summary; exit 0
}
ENVS_WITHOUT_SECURE=$(echo "${ENVIRONMENTS}" | jq '[.items[] | select(.secureMode == false)] | length')
TOTAL_ENVS=$(echo "${ENVIRONMENTS}" | jq '.items | length')
info "2.1 Total environments: ${TOTAL_ENVS}"
if [ "${ENVS_WITHOUT_SECURE}" -gt 0 ]; then
warn "2.1 Environments without secure mode:"
echo "${ENVIRONMENTS}" | jq -r '.items[] | select(.secureMode == false) | " - \(.key)"'
fi
# Enable secure mode on production environment
PROD_SECURE=$(echo "${ENVIRONMENTS}" | jq -r '.items[] | select(.key == "production") | .secureMode')
if [ "${PROD_SECURE}" = "true" ]; then
pass "2.1 Production environment has secure mode enabled"
increment_applied
else
info "2.1 Enabling secure mode on production..."
ld_semantic_patch "/projects/${LD_PROJECT_KEY}/environments/production" '{
"comment": "HTH: Enable secure mode to prevent client-side SDK user impersonation",
"instructions": [
{"kind": "updateSecureMode", "value": true}
]
}' || {
fail "2.1 Failed to enable secure mode on production"
increment_failed; summary; exit 0
}
pass "2.1 Secure mode enabled on production"
increment_applied
fi
2.2 API Token Security
Profile Level: L1 (Crawl) NIST 800-53: IA-5
Description
Inventory LaunchDarkly access tokens, remove unused ones, and issue new tokens scoped with the least-privilege role or inline policy the caller actually needs — choosing personal tokens for human-owned scripts and service tokens for automation that must survive offboarding.
Rationale
Why This Matters:
- API tokens authenticate automated access to the full LaunchDarkly REST API and can modify flags without a human login or MFA prompt
- Long-lived or unscoped tokens that leak give attackers persistent control over feature configuration
- Scoping tokens to least-privilege base roles, custom roles, or inline policies limits what a stolen token can do
- Service tokens are deliberately member-independent, so they keep working after the person who created them leaves — that durability is why they need a standing inventory and an owner of record
Attack Prevented: Token theft, persistent API access, unauthorized flag manipulation, MFA bypass via automation
Personal tokens and service tokens are not interchangeable. Personal tokens are bound to the member who created them: they can never exceed that member’s own permissions, and LaunchDarkly automatically deactivates them when the member is removed from the account. Service tokens (plan-dependent — see Appendix A) are independent of any member, and their permissions are fixed at creation and cannot be edited afterward — to change a service token’s access you must create a replacement and revoke the old one. Use service tokens for CI/CD and automation precisely because they survive offboarding, and compensate with an inventory that records each token’s owner and purpose. (Access tokens documentation)
ClickOps Implementation
Step 1: Audit Access Tokens
- Navigate to: gear icon (left sidebar) → Organization settings → Authorization
- In the Access tokens section, review every token, its role, and its owner
- Remove unused tokens
Step 2: Create Scoped Tokens and Rotate on a Cadence
- In the Access tokens section, click Create token, give it a human-readable Name, and assign the least-privilege Role — Reader, Writer, Admin, Owner, a custom role, or an inline policy
- Select This is a service token only for automation that must outlive its creator; leave it unchecked for a personal token
- LaunchDarkly’s token-creation flow does not offer an expiration date, so enforce lifetime yourself: set a written rotation cadence (for example, quarterly), record each token’s issue date in your inventory, and reset or delete tokens on schedule rather than relying on a platform expiry (Creating API access tokens)
Code Pack: Terraform
# Scoped read-only token for monitoring
resource "launchdarkly_access_token" "monitoring" {
name = "HTH Monitoring Read-Only"
service_token = true
inline_roles {
effect = "allow"
actions = ["viewProject"]
resources = ["proj/*"]
}
inline_roles {
effect = "deny"
actions = ["createFlag", "deleteFlag", "updateOn", "updateOff"]
resources = ["proj/*:env/*:flag/*"]
}
}
# Scoped token for a specific project/environment
resource "launchdarkly_access_token" "project_scoped" {
name = "HTH Project-Scoped Token"
service_token = true
inline_roles {
effect = "allow"
actions = ["viewProject", "updateOn", "updateOff"]
resources = ["proj/${var.project_key}:env/${var.environment_key}:flag/*"]
}
}
Code Pack: API Script
# List all tokens and check for overly permissive ones
TOKENS=$(ld_get "/tokens?showAll=true") || {
fail "2.2 Unable to retrieve access tokens"
increment_failed; summary; exit 0
}
TOTAL_TOKENS=$(echo "${TOKENS}" | jq '.items | length')
ADMIN_TOKENS=$(echo "${TOKENS}" | jq '[.items[] | select(.role == "admin")] | length')
NO_ROLE_TOKENS=$(echo "${TOKENS}" | jq '[.items[] | select(.role == "writer" or .role == "admin") | select(.serviceToken == true)] | length')
info "2.2 Total tokens: ${TOTAL_TOKENS}"
info "2.2 Admin-level tokens: ${ADMIN_TOKENS}"
info "2.2 Service tokens with write/admin: ${NO_ROLE_TOKENS}"
if [ "${ADMIN_TOKENS}" -gt 0 ]; then
warn "2.2 Tokens with admin role (should use scoped roles instead):"
echo "${TOKENS}" | jq -r '.items[] | select(.role == "admin") | " - \(.name // "unnamed") (id: \(._id))"'
fail "2.2 ${ADMIN_TOKENS} token(s) have admin role — scope down with custom roles"
increment_failed
else
pass "2.2 No tokens with admin role"
increment_applied
fi
Code Pack: Sigma Detection Rule
detection:
selection:
kind: 'token'
accesses.action: 'createToken'
condition: selection
fields:
- date
- title
- member.email
- target.name
- accesses.resource
3. Environment Security
3.1 Environment Segmentation
Profile Level: L1 (Crawl) NIST 800-53: CM-3
Description
Separate LaunchDarkly environments and require comments, review or approval, and change history for changes to production.
Rationale
Why This Matters:
- Isolating dev, staging, and production prevents test changes from accidentally altering live application behavior
- Required reviews and approvals add a human checkpoint before high-impact production flag changes take effect
- Mandatory comments and change history create an audit trail tying every change to a reason and an author
- Approval workflows stop a single compromised or careless account from unilaterally toggling production features
Attack Prevented: Unauthorized production changes, accidental misconfiguration, unreviewed flag flips, change repudiation
ClickOps Implementation
Step 1: Configure Environment Settings
- Navigate to: Project settings → Environments
- Configure:
- Require comments for changes
- Require review for production
- Enable change history
Step 2: Approval Workflows (Enterprise)
- Configure approval requirements
- Set minimum approvers
- Define bypass conditions
Code Pack: Terraform
# Hardened production environment with approval workflows
resource "launchdarkly_environment" "production" {
key = "production"
name = "Production"
color = "FF0000"
project_key = var.project_key
require_comments = true
confirm_changes = true
secure_mode = true
critical = true
default_track_events = true
approval_settings {
required = true
min_num_approvals = 2
can_review_own_request = false
can_apply_declined_changes = false
required_approval_tags = ["sensitive"]
service_kind = "launchdarkly"
}
tags = ["hth-hardened"]
}
# Staging environment — comments required, but no approval gate
resource "launchdarkly_environment" "staging" {
key = "staging"
name = "Staging"
color = "FFA500"
project_key = var.project_key
require_comments = true
confirm_changes = true
secure_mode = false
critical = false
}
Code Pack: API Script
# Enable require-comments, confirm-changes, and mark as critical
CURRENT=$(ld_get "/projects/${LD_PROJECT_KEY}/environments/production") || {
fail "3.1 Unable to retrieve production environment"
increment_failed; summary; exit 0
}
REQUIRE_COMMENTS=$(echo "${CURRENT}" | jq -r '.requireComments')
CONFIRM_CHANGES=$(echo "${CURRENT}" | jq -r '.confirmChanges')
IS_CRITICAL=$(echo "${CURRENT}" | jq -r '.critical')
INSTRUCTIONS="[]"
if [ "${REQUIRE_COMMENTS}" != "true" ]; then
INSTRUCTIONS=$(echo "${INSTRUCTIONS}" | jq '. + [{"kind": "updateRequireComments", "value": true}]')
fi
if [ "${CONFIRM_CHANGES}" != "true" ]; then
INSTRUCTIONS=$(echo "${INSTRUCTIONS}" | jq '. + [{"kind": "updateConfirmChanges", "value": true}]')
fi
if [ "${IS_CRITICAL}" != "true" ]; then
INSTRUCTIONS=$(echo "${INSTRUCTIONS}" | jq '. + [{"kind": "updateCritical", "value": true}]')
fi
if [ "$(echo "${INSTRUCTIONS}" | jq 'length')" -gt 0 ]; then
info "3.1 Applying environment hardening..."
PAYLOAD=$(jq -n --argjson inst "${INSTRUCTIONS}" '{
"comment": "HTH: Harden production environment controls",
"instructions": $inst
}')
ld_semantic_patch "/projects/${LD_PROJECT_KEY}/environments/production" "${PAYLOAD}" || {
fail "3.1 Failed to update production environment"
increment_failed; summary; exit 0
}
pass "3.1 Production environment hardened"
increment_applied
else
pass "3.1 Production environment already hardened"
increment_applied
fi
Code Pack: Sigma Detection Rule
detection:
selection:
kind: 'environment'
accesses.action:
- 'updateRequireComments'
- 'updateConfirmChanges'
- 'updateSecureMode'
- 'updateApprovalSettings'
filter_production:
accesses.resource|contains: 'env/production'
condition: selection and filter_production
fields:
- date
- title
- member.email
- accesses.action
- accesses.resource
- previousVersion
- currentVersion
3.2 Flag Security
Profile Level: L2 (Walk) NIST 800-53: CM-7
Description
Tag flags that control security-sensitive behavior, apply extra review to them, and restrict who can view and change targeting rules.
Rationale
Why This Matters:
- Flags that gate authentication, authorization, or other security controls can disable protections if flipped maliciously
- Tagging and extra review ensure security-relevant flags receive scrutiny proportional to their impact
- Restricting visibility of targeting rules prevents leakage of customer segments and internal business logic
- Monitoring rule changes detects enumeration or tampering attempts before they affect users
Attack Prevented: Security-control bypass, targeting-rule enumeration, business-logic disclosure, unauthorized flag tampering
Implementation
Step 1: Tag Sensitive Flags
- Tag flags controlling security features
- Apply additional review requirements
- Audit changes
Step 2: Targeting Rule Protection
- Limit who can view targeting rules
- Audit rule changes
- Monitor for enumeration
Code Pack: Terraform
# Example: Secure feature flag with restricted client-side exposure
resource "launchdarkly_feature_flag" "secure_flag_example" {
project_key = var.project_key
key = "hth-example-secure-flag"
name = "HTH Example Secure Flag"
description = "Demonstrates HTH flag security best practices"
variation_type = "boolean"
temporary = true
# Restrict client-side SDK exposure
client_side_availability {
using_environment_id = false
using_mobile_key = false
}
# Assign a team maintainer for lifecycle ownership
maintainer_team_key = var.maintainer_team_key
tags = ["sensitive", "hth-managed"]
variations {
value = true
name = "Enabled"
description = "Feature is active"
}
variations {
value = false
name = "Disabled"
description = "Feature is inactive"
}
defaults {
on_variation = 0
off_variation = 1
}
}
Code Pack: API Script
# Check for stale flags and unrestricted client-side exposure
FLAGS=$(ld_get "/flags/${LD_PROJECT_KEY}?summary=true&limit=100") || {
fail "3.2 Unable to retrieve flags"
increment_failed; summary; exit 0
}
TOTAL_FLAGS=$(echo "${FLAGS}" | jq '.items | length')
TEMP_FLAGS=$(echo "${FLAGS}" | jq '[.items[] | select(.temporary == true)] | length')
CLIENT_EXPOSED=$(echo "${FLAGS}" | jq '[.items[] | select(.clientSideAvailability.usingEnvironmentId == true)] | length')
NO_MAINTAINER=$(echo "${FLAGS}" | jq '[.items[] | select(._maintainer == null and ._maintainerTeam == null)] | length')
info "3.2 Total flags: ${TOTAL_FLAGS}"
info "3.2 Temporary flags: ${TEMP_FLAGS}"
info "3.2 Client-side exposed: ${CLIENT_EXPOSED}"
info "3.2 Without maintainer: ${NO_MAINTAINER}"
if [ "${NO_MAINTAINER}" -gt 0 ]; then
warn "3.2 ${NO_MAINTAINER} flag(s) have no assigned maintainer"
fi
if [ "${CLIENT_EXPOSED}" -gt 0 ]; then
warn "3.2 ${CLIENT_EXPOSED} flag(s) exposed to client-side SDKs — review for sensitive data"
fi
pass "3.2 Flag security audit complete"
increment_applied
4. Monitoring & Detection
4.1 Audit Log
Profile Level: L1 (Crawl) NIST 800-53: AU-2, AU-3
Description
Review the LaunchDarkly audit log regularly and export it to your SIEM to retain a durable record of all account, role, token, and flag changes.
Rationale
Why This Matters:
- The audit log records who changed which flag, role, token, or setting and when, enabling investigation and accountability
- Exporting to a SIEM preserves events beyond the platform retention window and correlates them with other security telemetry
- Without centralized monitoring, malicious flag changes and token abuse can go undetected until they cause harm
- Audit evidence supports SOC 2, ISO 27001, and other compliance obligations for change management
Attack Prevented: Undetected tampering, repudiation, delayed incident detection, audit-trail gaps
ClickOps Implementation
Step 1: Access Audit Log
- Navigate to: Account settings → Audit log (LaunchDarkly renamed the account settings area to Organization settings, reached via the gear icon in the left sidebar; the audit log documentation page was not publicly resolvable during this pass, so verify the exact path in your own account)
- Review changes
- Configure SIEM export
Detection Focus
Code Pack: Terraform
# Splunk audit log subscription
resource "launchdarkly_audit_log_subscription" "splunk" {
integration_key = "splunk"
name = "HTH Splunk Audit Stream"
on = true
config = {
base_url = var.splunk_hec_url
token = var.splunk_hec_token
}
statements {
effect = "allow"
actions = ["*"]
resources = ["proj/*"]
}
tags = ["hth", "siem"]
}
# Signed webhook for custom SIEM
resource "launchdarkly_webhook" "siem" {
name = "HTH SIEM Webhook"
url = var.siem_webhook_url
on = true
secret = var.webhook_signing_secret
statements {
effect = "allow"
actions = ["*"]
resources = ["proj/*"]
}
tags = ["hth", "siem"]
}
Code Pack: API Script
# Create a signed webhook for SIEM audit log streaming
EXISTING=$(ld_get "/webhooks" | jq -r '.items[] | select(.name == "HTH SIEM Webhook") | ._id') || true
if [ -n "${EXISTING}" ]; then
info "4.1 SIEM webhook already exists (id: ${EXISTING})"
pass "4.1 Audit log webhook configured"
increment_applied
else
WEBHOOK_SECRET="${LD_WEBHOOK_SECRET:-$(openssl rand -hex 32)}"
info "4.1 Creating signed webhook for SIEM..."
RESPONSE=$(ld_post "/webhooks" "$(jq -n \
--arg url "${LD_SIEM_WEBHOOK_URL}" \
--arg secret "${WEBHOOK_SECRET}" \
'{
"name": "HTH SIEM Webhook",
"url": $url,
"sign": true,
"secret": $secret,
"on": true,
"tags": ["hth", "siem"],
"statements": [
{
"effect": "allow",
"actions": ["*"],
"resources": ["proj/*"]
}
]
}'
)") || {
fail "4.1 Failed to create SIEM webhook"
increment_failed; summary; exit 0
}
pass "4.1 SIEM webhook created"
info "4.1 Webhook signing secret: ${WEBHOOK_SECRET}"
increment_applied
fi
Code Pack: Sigma Detection Rule
detection:
selection_webhook:
kind: 'webhook'
accesses.action:
- 'deleteWebhook'
selection_integration:
kind: 'integration'
accesses.action:
- 'deleteIntegration'
condition: selection_webhook or selection_integration
fields:
- date
- title
- member.email
- target.name
- accesses.resource
Appendix A: Edition Compatibility
LaunchDarkly’s public pricing page lists three plan tiers — Developer, Foundation, and Enterprise. The “Pro” tier this guide previously referenced no longer appears in LaunchDarkly’s plan lineup.
| Control | Developer | Foundation | Enterprise |
|---|---|---|---|
| SAML SSO | ✅ | ✅ | ✅ |
| SCIM provisioning | ❌ | ❌ | ✅ |
| Custom Roles | ❌ | ❌ | ✅ |
| Approval Workflows | ❌ | ❌ | ✅ |
| Service tokens | ❌ | ❌ | ✅ |
| Default initial role “No access” | ❌ (Reader) | ❌ (Reader) | ✅ |
Two LaunchDarkly sources disagree on tier naming — verify against your own contract. The pricing page lists Developer / Foundation / Enterprise, while the SSO documentation attributes SCIM and team sync to a “Guardian” tier that does not appear in the published pricing lineup. Both are first-party LaunchDarkly sources, so this guide records the disagreement rather than resolving it: treat SCIM, custom roles, service tokens, and approvals as top-tier-only capabilities and confirm the exact plan name on your contract or with your account team before planning around them. (Pricing · SSO documentation)
Appendix B: References
Official LaunchDarkly Documentation:
- LaunchDarkly Documentation
- Single sign-on (SSO)
- API access tokens
- Creating API access tokens
- Creating custom roles
- Plans and pricing
API & Developer Resources:
Compliance Frameworks:
- SOC 2 Type II, ISO 27001, ISO 27701, FedRAMP Moderate ATO, HIPAA – compliance reports available upon request via LaunchDarkly Support
Security Incidents:
- No major public security breaches identified as of this writing.
Changelog
| Date | Version | Maturity | Changes | Author |
|---|---|---|---|---|
| 2026-08-08 | 0.2.0 | draft | Currency pass: correct plan tiers to Developer/Foundation/Enterprise with a both-sources note on the “Guardian” naming conflict; update console paths to Organization settings (SSO, Roles, Authorization verified against current docs; audit-log path annotated as unverified); add SSO default-initial-role and single-IdP callouts; distinguish personal vs service tokens and replace the unsupported token-expiration step with a manual rotation cadence; replace Trust Center/security-program links with first-party configuration docs | Claude Code (Opus 4.8) |
| 2026-06-29 | 0.1.1 | draft | Add cheat-sheet Description and Rationale for all controls | Claude Code (Opus 4.8) |
| 2025-12-14 | 0.1.0 | draft | Initial LaunchDarkly hardening guide | Claude Code (Opus 4.5) |
Contributing
Found an issue or want to improve this guide?
- Report outdated information: Open an issue with tag
content-outdated - Propose new controls: Open an issue with tag
new-control - Submit improvements: See Contributing Guide