Secure by Design
A development philosophy in which security is embedded in the architecture and code from the very beginning—not as an after-the-fact patch. Secure-by-design principles include a minimal attack surface, secure defaults, defense in depth, and fail-safe defaults.
Secure by Design is more than just a buzzword—it is a rejection of the "security retrofit" mentality. When a product is first developed, then "tested" for security, and finally patched, structural vulnerabilities arise that cannot be fixed. Secure by Design reverses this order: threat modeling before the first line of code.
The CISA Secure by Design Principles (2023)
In 2023, the U.S. agency CISA, together with international security agencies (including Germany’s BSI), published principles for Secure by Design:
Principle 1: Manufacturers take responsibility for security
- Not: “Users must configure securely”
- Instead: Secure defaults out of the box
- Example: Passwords not set to "admin/admin" by default, but randomly generated or required to be changed
Principle 2: Radical Transparency and Accountability
- CVE disclosure without delay
- Clear EOL (End of Life) dates
- Security changelogs in release notes
Principle 3: Customer security as a core objective
- Security is not a premium feature
- Not: "MFA is available in the Enterprise version"
- But: MFA is standard and free
The six Secure-by-Design principles
1. Attack Surface Reduction
Every unnecessary feature is a potential vulnerability.
- Install only what is needed
- Ports/services: open only the minimum
- Wrong: 12 services at startup, users configure them
- Right: 3 core services, users activate them optionally
# All ports bound by default:
ss -tlnp # Question every line: Do you need this?
2. Secure Defaults
Secure out of the box, not "can be configured securely."
<!-- FALSCH: -->
<encryption enabled="false">
<!-- Default in config.xml -->
CORRECT: Encryption ALWAYS, no opt-out for data at rest
WRONG: Admin UI default set to 0.0.0.0 (all interfaces)
CORRECT: Admin UI default set to 127.0.0.1 (local only)
3. Defense in Depth
No single layer of protection is sufficient. If Layer 1 fails, Layer 2 still holds.
Web App Example:
| Layer | Measure | Protects against |
|---|---|---|
| Layer 1 | WAF | Blocks known attacks |
| Layer 2 | Input Validation | Blocks injection |
| Layer 3 | Prepared Statements | Blocks SQLi even if Layer 2 fails |
| Layer 4 | Minimal DB privileges | Limits damage if Layer 3 fails |
| Layer 5 | Encryption at rest | Limits damage if Layer 4 fails |
4. Fail-Safe (Safe Behavior in Case of Errors)
In case of error: block by default, do not allow.
// WRONG:
try { checkPermission(user, resource); }
catch (Exception e) {
log.error("Permission check failed");
return true; // Grant access if an error occurs!
}
// CORRECT:
try { return checkPermission(user, resource); }
catch (Exception e) {
log.error("Permission check failed - denying access");
return false; // If an error occurs: NO access
}
5. Least Privilege
Every component/process/user is granted only the minimum necessary permissions:
- DB users have only SELECT access to necessary tables
- Containers do not run as root
- API keys have read-only scope unless write access is necessary
6. Psychological Acceptance (Usability)
Secure options must be the easiest option. If security is cumbersome, the user will bypass it. Avoid "security theater" (looks secure, but isn’t).
Threat Modeling - Security Before Code
When: Before implementation, during the design phase Who: Developers + Security Expert + Architect Output: List of threats → Mitigations → Requirements
STRIDE Method (Microsoft)
| Letter | Category | Description |
|---|---|---|
| S | Spoofing | Attacker impersonates someone else |
| T | Tampering | Attacker manipulates data |
| R | Repudiation | Attacker denies actions |
| I | Information Disclosure | Data leak |
| D | Denial of Service | Crippling the system |
| E | Elevation of Privilege | Gaining privileges |
STRIDE for a simple REST API
Endpoint: POST /api/orders (Place order)
| Category | Threat | Mitigation |
|---|---|---|
| Spoofing | Attacker places an order as another user | Strong authentication (JWT + audience check), MFA for large amounts |
| Tampering | Attacker changes order price in request | Always calculate prices on the server side, never on the client |
| Repudiation | "I never ordered this" – no evidence | Audit log with user ID, timestamp, IP, request hash |
| Info Disclosure | Error message reveals DB schema | Generic error messages in production, details only in logs |
| DoS | 10,000 orders/second → DB overloaded | Rate limiting (100/min per user), queue for processing |
| EoP | Attacker inserts admin=true into POST body | Whitelist input validation, DTO mapping without admin field |
Secure-by-Design in Practice: Code Examples
// ===== INSECURE: Classic Errors =====
// 1. SQL Injection via String Concatenation
const query = `SELECT * FROM users WHERE email = '${userInput}'`;
// Payload: ' OR '1'='1 → returns all users
// 2. Error message leaks system information
app.use((err, req, res, next) => {
res.json({ error: err.stack }); // Stack trace including file paths!
});
// 3. IDOR: User accesses a third-party resource
app.get('/invoice/:id', (req, res) => {
const invoice = db.query('SELECT * FROM invoices WHERE id = ?', req.params.id);
res.json(invoice); // No validation: does the invoice belong to the user?
});
// ===== SECURE: Secure-by-Design =====
// 1. Prepared Statements (SQLi-safe)
const query = 'SELECT * FROM users WHERE email = ?';
const user = await db.query(query, [userInput]);
// 2. Error handling without info leak
app.use((err, req, res, next) => {
const errorId = crypto.randomUUID();
logger.error({ errorId, stack: err.stack, path: req.path });
res.status(500).json({
error: 'An internal error occurred',
errorId // For reference in the log, without details
});
});
// 3. Ownership Check (IDOR-safe)
app.get('/invoice/:id', requireAuth, async (req, res) => {
const invoice = await db.query(
'SELECT * FROM invoices WHERE id = ? AND user_id = ?',
[req.params.id, req.user.id] // ALWAYS check user_id!
);
if (!invoice) return res.status(404).json({ error: 'Not found' });
res.json(invoice);
});
// 4. Input Validation (Zod Schema)
import { z } from 'zod';
const OrderSchema = z.object({
productId: z.string().uuid(),
quantity: z.number().int().min(1).max(100),
// No 'price' or 'admin' field - will be ignored if sent
});
app.post('/orders', requireAuth, async (req, res) => {
const result = OrderSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: result.error.issues });
}
const { productId, quantity } = result.data; // Only validated data!
// Retrieve price from the DB on the server side:
const product = await db.getProduct(productId);
const totalPrice = product.price * quantity;
// ...
});
Secure by Default - Checklist for New Projects
Authentication
- Passwords: bcrypt/Argon2id (no MD5, SHA-1, unsalted!)
- Session tokens: cryptographically random (
crypto.randomUUID) - JWT: short validity period (Access: 15 min, Refresh: 7 days)
- MFA: offer as an option, enforce for admins
- Password reset: time-limited, one-time tokens
Authorization
- RBAC or ABAC: explicit access rights
- Ownership check: do resources belong to the requesting user?
- Deny-by-default: no access unless explicitly allowed
- Admin functions: separate auth check
Input/Output
- Input validation: schema-based (Zod, Joi, Pydantic)
- Output encoding: XSS protection (React: automatic; otherwise escape)
- SQL: prepared statements or ORM
- Error messages: generic for users, detailed in logs
Configuration
- Secrets: environment variables (no hardcoding, no Git!)
- HTTPS: enforce (HSTS)
- Security headers: CSP, X-Frame-Options, X-Content-Type
- CORS: allowed origins only
- Debug mode: disabled in production
Logging & Monitoring
- Log auth events (login OK/fail, password reset)
- Log access control (denied events)
- No sensitive data in logs (passwords, tokens)
- Log level in production: INFO/WARN/ERROR (no DEBUG)
CISA Secure by Design Pledge (2024)
CISA has called on software vendors to voluntarily meet the following goals within one year:
- Measurably increase the proportion of MFA in their own products
- Eliminate default passwords in all products
- Reduce the number of critical CVEs in their own product portfolio
- Establish transparency regarding the vulnerability disclosure process
- Publish CVSSv3 scores for all vulnerabilities
As of 2025:
- 250+ companies have signed
- Including: Microsoft, Google, AWS, Cisco, Palo Alto Networks
- BSI endorses the approach for German companies
Implications for Purchasing/Procurement:
- Give preference to products from Secure-by-Design Pledgers
- Contract clauses: Manufacturers are liable for known vulnerabilities
- Contractually secure EOL guarantees