Today’s Lesson
Security for Legal SaaS — Episode 48: Environment Configuration and Secure Defaults
The Difference Between Staging and Production Should Be Credentials and Scale — Not Security Controls
Your legal SaaS platform runs in multiple environments. Development, where engineers build and test features. Staging, where the team validates before release. Production, where real law firms use the platform with real client data. The security controls in each environment should be identical. The only differences should be credentials (each environment has its own database passwords, API keys, and service accounts) and scale (production has more servers, more capacity).
In practice, this is rarely the case. Teams disable authentication in development "to make testing easier." They skip rate limiting in staging "because it slows down integration tests." They disable audit logging in development "because it fills up disk space." Each shortcut creates a gap between what gets tested and what runs in production. Bugs that only manifest with security controls enabled — race conditions in authentication, edge cases in rate limiting, performance impacts from audit logging — go undetected until they're in production with real client data.
Red flag: "We only enable auth in production" is not a shortcut. It means your entire development and testing process runs without the security controls that production depends on. You are testing a different application than what your clients use.
Configuration Management — Separating Config from Code
The Twelve-Factor App methodology 1 — an influential set of principles for building modern web applications — establishes the foundational rule: configuration that varies between environments must be stored separately from code. Database URLs, API keys, feature flags, service endpoints — these should never be hardcoded.
Three mechanisms dominate in practice:
Environment Variables
The simplest approach. Your application reads configuration from environment variables set by the deployment platform. The Twelve-Factor App 1 recommends this as the primary method because environment variables are language-agnostic and can be set without modifying code or config files.
DATABASE_URL=postgres://user:pass@db.prod.internal:5432/legaldb
REDIS_URL=redis://cache.prod.internal:6379
AUTH_ENABLED=true
RATE_LIMIT_ENABLED=true
AUDIT_LOG_LEVEL=full
The security concern: environment variables can be visible in process listings, debug outputs, and crash reports. Recent guidance 2 acknowledges that for secrets, environment variables alone are insufficient — they should be combined with a secrets manager.
Sealed Secrets and Config Maps
In Kubernetes environments (the container orchestration platform introduced in Episode 15), ConfigMaps 3 store non-sensitive configuration and Secrets 4 store sensitive values. Sealed Secrets encrypt the Secret data so it can be safely stored in version control — only the cluster can decrypt them.
This matters for legal SaaS because it enables a GitOps workflow — all configuration, including secrets, is version-controlled and auditable. You can trace who changed a configuration value, when, and why. When a regulator asks "who changed the encryption key rotation period?", you can point to a specific commit with a reviewer's approval.
External Secrets Managers
For production credentials, HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, and Azure Key Vault provide centralised, audited, rotation-capable secrets management. The application authenticates to the secrets manager at startup and retrieves only the credentials it needs. Building Secure 12-Factor Apps 5 recommends this as the standard for any application handling sensitive data.
Secure Defaults — The Out-of-the-Box Configuration Should Be Secure
A "default" is the behaviour your system exhibits when no explicit configuration is provided. Secure defaults mean that the out-of-the-box configuration prioritises safety over convenience.
| Setting | Insecure Default | Secure Default |
|---|---|---|
| Authentication | Disabled in dev | Enabled everywhere |
| HTTPS | Optional | Required (redirect HTTP to HTTPS) |
| CORS | Allow all origins | Allow only your domains |
| Debug mode | On | Off (require explicit flag to enable) |
| Session timeout | None | 30 minutes idle, 8 hours absolute |
| Logging | Minimal | Full audit logging |
| Rate limiting | Disabled | Enabled with sensible thresholds |
| CSP headers | None | Strict policy |
NIST SP 800-53 controls CM-6 and CM-7 6 specifically address configuration management and least functionality, requiring that systems be configured to provide only essential capabilities and that security-relevant settings default to the most restrictive mode consistent with operational requirements.
For legal SaaS, consider what happens when a new developer joins and spins up a local development environment. If the defaults are insecure, that developer's first experience is running the application without the security controls that production depends on. Every test they write, every feature they build, every integration they validate — all without authentication, rate limiting, or audit logging. When those features reach production, they encounter security controls for the first time. This is where "it works on my machine" becomes a security incident.
Design principle: Make the secure path the easy path. If a developer has to do extra work to make the application less secure, most won't bother. If they have to do extra work to make it more secure, many won't.
Environment Parity — Security Controls in Every Environment
Environment parity means every environment — development, staging, production — runs the same security controls. The controls may use different credentials, different data, different scale. But the mechanisms are present and active.
Practical implementation:
- Authentication in development — use a local identity provider or test accounts, but keep the authentication middleware active. Developers should log in to the application, even locally.
- Rate limiting in staging — use higher thresholds if needed for integration testing, but keep the rate limiter active. Tests should work within rate limits, not around them.
- Audit logging everywhere — log to a local file in development, to a managed service in production. But log in both. If audit logging causes performance issues, that's a bug to fix, not a reason to disable it.
- TLS in development — use self-signed certificates or a local CA like mkcert 7. Developers should experience the same HTTPS behaviour as production, including mixed-content warnings and secure cookie behaviour.
Feature flag platforms like Unleash and LaunchDarkly 8 can manage environment-specific configurations while maintaining security parity. A feature flag can control whether a new payment flow is enabled, while security controls like authentication and rate limiting remain always-on and unflagged.
Feature Flags for Security Controls
Feature flags — configuration switches that enable or disable functionality at runtime without deploying new code — are a powerful tool for gradual security rollouts. Introducing a new Content Security Policy? Roll it out to 5% of users first, monitor for breakage, then increase.
But feature flag security best practices 8 warn against a dangerous anti-pattern: using feature flags to disable security controls in specific environments. A flag called `ENABLE_AUTH` that is `false` in development and `true` in production means authentication is a toggleable option, not a foundational control.
The Octopus Deploy guide to feature flag best practices 9 recommends:
- Never flag core security controls. Authentication, authorization, encryption, and audit logging should be always-on, not toggleable.
- Flag new security features during rollout. A new CSP policy, a new rate limiting algorithm, a new session management approach — these can be flagged during the transition period.
- Require review for flag changes in production. Treat production flag changes like code deployments — they need approval and audit trails.
- Clean up flags aggressively. A feature flag that has been 100% enabled for three months is not a flag — it's dead code that someone might accidentally disable.
Unleash's DevOps compliance guide 10 notes that feature flag management systems should be treated as "Tier-1 control planes with strict Role-Based Access Control (RBAC) and least privilege principles." For legal SaaS under SOC 2 or ISO 27001, the feature flag system itself is a security-critical component.
The Danger of Environment-Specific Security
When security controls behave differently across environments, three things go wrong:
- Bugs hide. A race condition in your authentication middleware only manifests under load. You never see it in development because authentication is disabled. It appears in production during a demo to a major law firm.
- Developers build around security. If authentication is disabled locally, developers write API integration tests that skip auth headers. Those tests pass in CI but fail — or worse, expose real vulnerabilities — when auth is enabled.
- Configuration drift accumulates. Each environment diverges. Staging gets a "temporary" exception to CORS rules. Development gets debug mode permanently enabled. Over time, the environments become so different that staging is no longer a reliable predictor of production behaviour.
CloudBees' guide to securing feature-flag-based applications 11 reinforces that "compliance demands strict environment segregation" — but segregation means different credentials and data, not different security controls.
Key takeaway: Secure defaults and environment parity are not DevOps best practices. They are security requirements. The out-of-the-box configuration of your legal SaaS platform should be secure. Security controls should be active in every environment. The difference between staging and production should be credentials and scale — never whether authentication, rate limiting, or audit logging is enabled. If your development environment doesn't enforce security, your development process is testing a different application than what your clients use.
What's Next
Next episode, we wrap up Module 10 with cloud IAM and least privilege — the identity and access management controls that determine who (and what) can access your cloud resources, and why "that service account has Owner permissions because someone needed it once" is one of the most common and dangerous patterns in cloud security.
Sources & references
- The Twelve-Factor App — Config
- Proposal to Modify Factor Config — Twelve-Factor GitHub
- ConfigMaps — Kubernetes Documentation
- Secrets — Kubernetes Documentation
- Building Secure 12-Factor Apps — Think DevOps
- NIST SP 800-53 Rev. 5 — Configuration Management Controls
- mkcert — Local HTTPS Development
- Feature Flag Security Best Practices — Unleash
- The 12 Commandments of Feature Flags — Octopus Deploy
- DevOps Regulatory Compliance: Feature Flags for Auditing and Governance — Unleash
- How to Secure Your Feature-Flag-Based Application — CloudBees
- Feature Flagging for Security: Best Practices — ConfigCat