Brutalist Application Security: A Guide
Brutalist Application Security embraces a philosophy of transparency, visible and unapologetic security measures, and infrastructure-aware controls. It prioritizes clarity over subtlety and enforces protection, even when the implementation feels rough or intrusive. This approach is designed for modern tech stacks with CI/CD pipelines supporting both cloud and local development environments.
Core Philosophy
Security Brutalism in application security focuses on truth over comfort. The fundamental principle is simple: make security rules visible, inflexible, and codified into your developer lifecycle, both locally and in the cloud.
Key tenets:
- No magic boxes: Understand your defenses completely
- Visible friction: Let developers feel the security edge—don't hide it behind UX niceties
- Fail loudly: Security failures should be unambiguous and impossible to ignore
- Code-driven policy: Enforce policies as code, not through tribal knowledge or Slack messages
Six Core Security Brutalism Tenets in Practice
1. Make Security Controls Visible and Unignorable
Fail loudly and early: If a security check fails in CI/CD, make the error unambiguous and impossible to ignore.
- Reject secrets in commits with a red-flashing CLI
- Display security failures prominently in build outputs
- Block pipeline progression until security gates pass
Force exposure of controls in developer workflows: Don't hide security checks in background processes.
- Run security linting and static analysis both locally and in CI
- Make developers experience the friction directly
- Security gates should be part of the default developer workflow, not optional add-ons
2. Immutable, Auditable, and Declarative Pipelines
Embrace declarative CI/CD where security configuration is code-reviewed and version-controlled:
- Use GitHub Actions, GitLab CI, or CircleCI with security configs stored alongside app code
- Make all security gates (SAST, DAST, dependency checks, infrastructure scanning) part of the default pipeline
- Block merges until security checks pass, no exceptions
3. Don't Trust Developer Devices, Verify Everything
For local development:
- Use pre-commit hooks to enforce secure coding patterns
- Employ containerized dev environments to isolate code from local system entropy
- Use signed commits (
git commit -S
) and enforce verification in the pipeline
Security Brutalist twist: If you can't verify commit signatures, reject the merge. Make that policy loudly known to all developers.
4. Zero Implicit Trust in CI/CD
Secure secrets management:
- Use external secret managers (HashiCorp Vault, AWS Secrets Manager) instead of CI/CD environment variables alone
- Enforce signed artifacts and implement SBOM (Software Bill of Materials) tracking
- Use runtime attestations for all deployments
Cryptographic verification:
- Sign everything in the pipeline
- Make developers see the cryptographic chain instead of hiding it behind trust-on-first-use assumptions
- Verify signatures at every stage of the deployment process
5. Loud, Inflexible Policy Enforcement
Treat policies like WAFs or firewalls: brutal, deterministic, unapologetic.
Examples of inflexible enforcement:
- If you use an unapproved base image, the build fails. No exceptions
- If a dependency is unpinned, your PR is rejected automatically
- If Terraform tries to open port 22 to 0.0.0.0/0, you get a Slack alert and the pipeline blocks
- Invalid input is rejected outright, if it doesn't match the rules, it doesn't get in
6. Expose Risk Visibly in Git and UI
Bot-driven transparency: Have bots post visible messages on PRs about security debt:
This PR introduces 3 new dependencies, including one with known CVEs (CVSS 8.3). Link to triage: [Security Dashboard]
Label-based classification: Label pull requests with security impact levels:
- SEC-LOW: Minor security implications
- SEC-HIGH: Significant security review required
- SEC-REVIEW-BLOCKED: Cannot proceed without security approval
Fundamental Security Controls
Input Validation
- Validate ALL input, strictly: Reject bad data outright
- Every piece of user-provided data is treated as potentially hostile
- Trust nothing, verify everything within the application itself
Authentication and Authorization
- Authenticate strongly: Use robust authentication mechanisms
- Authorize directly in code: Implement least privilege always
- Grant only what's absolutely necessary within the application itself
Output Encoding
- Encode ALL output: Prevent injection at the source
- Every output must be properly encoded for its context
- Treat all data as potentially dangerous until properly handled
Error Handling and Logging
- Handle errors cleanly, log verbosely (securely): Visibility is key
- Fail close: Errors should inform without exposing secrets
- Logs are the unvarnished truth of what's happening—make them comprehensive
Dependency Management
- Minimize dependencies: Less code, fewer holes
- Audit ruthlessly: Every external library is a potential vulnerability
- Patch constantly: Use the minimum required dependencies and keep them updated without fail
Automated Security Testing
- Automate basic security tests early: Catch obvious flaws early and often
- Provide raw feedback directly to developers in the development pipeline
- Integration should be seamless but visible
Secure Deployment
- Deploy securely via code: Minimal manual configuration
- Consistency enforced: Infrastructure and application deployment must be repeatable and defined
- No series of manual tweaks—everything should be declarative
Security Brutalist CI/CD Pipeline Example
[Developer Local Environment]
↓
Containerized dev environment (Gitpod/Codespaces)
↓
Pre-commit hooks run:
- Semgrep (SAST)
- Secrets scanner
- Dependency check
↓
Signed commit pushed → Git repository
↓
[CI/CD Pipeline Triggers]
↓
Security Gates (all must pass):
- Static Application Security Testing (SAST)
- Dependency vulnerability scanning
- Container image scanning
- Infrastructure as Code security scanning
- Secret detection verification
↓
Build and test (if security gates pass)
↓
Signed artifact creation with SBOM
↓
Runtime attestation and deployment
Implementation Guidelines
Accept Friction as a Signal, Not a Flaw
- Security friction indicates that protective measures are working
- Don't try to hide or minimize legitimate security controls
- Educate developers on why the friction exists and what it prevents
Be Transparent About Failures
- When security checks fail, explain exactly what failed, how, and why
- Provide clear remediation steps
- Make failure messages actionable, not just informative
Enforce Policies as Code
- All security policies should be version-controlled and code-reviewed
- Use infrastructure as code for security configurations
- Avoid manual processes or tribal knowledge for security decisions
Monitor and Measure
- Track security gate failure rates and remediation times
- Measure developer productivity impact and optimize where possible
- Monitor for security policy violations and exceptions
Final Security Brutalist Recommendations
- Do not hide security behind UX niceties: Let developers feel the edge of security controls
- Enforce policies as code: Not by tribal knowledge or Slack messages
- Accept friction as a signal: Not a flaw in the system
- Be transparent: About what failed, how, and why
- Build solid foundations: Security isn't a product you buy; it's a fundamental way you build
End Goal
The ultimate objective is creating applications where:
- What you see is what's enforced: No hidden security through obscurity
- What breaks doesn't collapse the system: Robust error handling and graceful degradation
- What remains is strong and recoverable: Resilient architecture that can withstand attacks
Security Brutalism focuses on truth over comfort. Make the security rules visible, inflexible, and woven into every aspect of your development lifecycle. The result is applications that are genuinely secure, not just compliant.