⚡ TL;DR: Treat security as a property of the whole system, not a final feature. You leave with the engineer's defaults: defense in depth, least privilege, encryption everywhere, real secrets management, and shifting left from day one.
Contents
- Security is not a feature you bolt on
- What 'non-functional requirement' actually means
- Defense in depth, assume each layer will fail
- Least privilege, the smallest possible blast radius
- Secure defaults & encryption everywhere
- The shared responsibility model & shifting left
- Common mistakes that cost hours (or careers)
- Where to go next
Security is not a feature you bolt on
Picture the classic project plan: build all the features, then "do a security pass" the week before launch. It never works. By then the architecture is set, permissions are wide open "to make it work," secrets are hardcoded, and the security pass becomes a frantic list of things that can't be fixed without rewrites. Security treated as a final checkbox is security that fails.
The senior mental shift is to treat security as a non-functional requirement, a property of the system like performance or reliability, present in every decision from day one. You don't "add" reliability at the end, and you can't add security at the end either. This article is the engineer's working model: the principles that make security a default rather than an afterthought.
📌 Who this is for: Engineers who build things and want security to be instinctive rather than a separate team's problem. No security background needed, every principle is concrete and immediately applicable.
What 'non-functional requirement' actually means
Functional requirements are what the system does. Non-functional requirements are how well it does them, fast, available, and secure. Users notice functional features; they only notice non-functional ones when they're missing, and with security, by then it's a breach.
| In the real world | In tech |
|---|---|
| 🏰 Concentric castle walls, not one gate | Defense in depth |
| 🔑 A key that opens only your office | Least privilege |
| 🔒 Doors that lock when they close | Secure defaults |
| 🛡️ A safe inside a locked room | Encryption + secrets management |
Security is a property of the whole building, not a gadget you install last.
The rest of this article is those four pictures turned into engineering practice. None of them is exotic, they're the boring, repeatable habits that separate systems that get breached from systems that don't.
Defense in depth, assume each layer will fail
A medieval castle didn't rely on one wall. It had a moat, an outer wall, an inner wall, and a keep, so breaching one defence still left you facing the next. Defense in depth applies the same logic: never rely on a single control, because any single control can fail. Assume your firewall will be misconfigured one day, and make sure that alone doesn't mean game over.
- Network, private subnets, security groups, a WAF in front of public endpoints.
- Identity, strong authentication, MFA, scoped access tokens.
- Application, input validation, output encoding, dependency scanning.
- Data, encryption at rest and in transit, so stolen storage is useless.
- Monitoring, audit logs and alerts so a breach is detected, not discovered months later.
The test of good design: "if an attacker gets past this layer, what stops them next?" If the honest answer is "nothing," you have a single point of security failure, and that's exactly what defense in depth exists to eliminate.
Least privilege, the smallest possible blast radius
Least privilege means every user, service, and process gets exactly the permissions it needs to do its job, and not one more. Why it matters is simple: when (not if) a credential leaks or a service is compromised, the damage is bounded by what that identity could do. An over-permissioned service account is a breach waiting to be amplified into a catastrophe.
iam-policy.json
// ❌ Convenient, catastrophic. One leaked key owns everything.
{ "Effect": "Allow", "Action": "*", "Resource": "*" }
// ✅ Least privilege. This service can only read one bucket.
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::app-uploads/*"
}
⚠️ Wildcard permissions are the #1 cloud finding:
Action: "*"onResource: "*"is the most common serious misconfiguration in cloud security audits. It's added "temporarily to unblock" and never removed. Start every policy from zero and add only what fails without it, deny by default, grant deliberately.
Secure defaults & encryption everywhere
Most people never change a default. So the safest system is one where the default is the secure choice, the database isn't publicly reachable unless you explicitly open it, the storage bucket is private until you make it public, the new user has no permissions until you grant them. Security that requires extra effort gets skipped; security that's the path of least resistance gets used.
Encryption in transit and at rest
Two flavours, both non-negotiable. In transit: every connection uses TLS, so traffic can't be read or tampered with on the wire, no plain HTTP, even internally. At rest: data on disk is encrypted, so a stolen drive or snapshot is useless without the key. Both are nearly free to enable in the cloud and there is no good reason to skip either.
Secrets management
Passwords, API keys, and tokens must never live in code or in a committed .env file, git history is forever, and a leaked key in a public repo is scanned and abused within minutes. Use a dedicated secrets manager (AWS Secrets Manager, Vault, your platform's secret store), inject secrets at runtime, and rotate them regularly. Treat any secret that ever touched a repo as already compromised.
💡 Tip: Add a pre-commit secret scanner (gitleaks, trufflehog) to your repos today. The cheapest possible breach prevention is catching the key before it's committed, not after it's been exploited.
The shared responsibility model & shifting left
A dangerous assumption sinks a lot of teams: "we're in the cloud, so security is handled." The cloud provider operates the shared responsibility model, they secure the cloud itself (the hardware, the data centre, the hypervisor); you secure what you put in it (your data, your access policies, your application, your configuration). The provider gives you locks; locking the doors is on you. Most cloud breaches are customer misconfigurations, not provider failures.
Shifting left means moving security earlier in the development process, left on the timeline. Instead of a security review at the end, you scan dependencies in CI, lint infrastructure-as-code for misconfigurations, run static analysis on every pull request, and check for secrets before merge. Catching a vulnerability in code review costs minutes; catching it in production costs an incident.
Common mistakes that cost hours (or careers)
-
Hardcoded secrets in code or
.envfiles committed to git. History is permanent and public repos are scanned constantly. Use a secrets manager and a pre-commit scanner. -
Wildcard IAM permissions.
*on actions and resources turns any single compromise into a total one. Grant the minimum and audit regularly. - Relying on a single security control. One firewall, one WAF, one anything. Layer defences so failing one isn't fatal.
- Assuming the cloud provider secures your data. They secure the infrastructure; your config and access are your job. Most breaches are misconfigurations.
- Treating security as a launch-week checkbox. By then the architecture is fixed and the fixes are rewrites. Bake it in from the first design decision.
Where to go next
The whole article in 6 lines
- Security is a non-functional requirement, a property of the whole system, present in every decision, not a final pass.
- Defense in depth: never rely on one control; assume each layer can fail and put another behind it.
- Least privilege bounds the blast radius of every leaked credential, deny by default, grant deliberately.
- Secure defaults win because nobody changes defaults; make the safe choice the easy one.
- Encrypt in transit and at rest, and keep secrets out of code with a real secrets manager.
- Know the shared responsibility model (your config is your job) and shift security left into CI.
These principles are the foundation; identity and network design are where you apply them most. Go deeper:
- Master the identity layer behind least privilege: Cloud IAM from First Principles.
- Take defense in depth to its logical conclusion, trust nothing: Zero Trust Networking for Beginners.
- Extend secure defaults to your build pipeline and dependencies: Securing the Software Supply Chain.
- Read the deeper lesson with exercises: Service Auth & IAM.
Run a secret scanner over one repo and audit one IAM policy for wildcards this week. Those two habits prevent a startling share of real breaches.
Originally published on TheSimplifiedTech, where this guide is interactive, with in-browser terminal labs and diagrams. Learn cloud and DevOps by doing, no videos.
Top comments (0)