Skip to main content

Command Palette

Search for a command to run...

The Security Checklist I Build Into Every Rust Backend From Day One

Updated
6 min readView as Markdown
The Security Checklist I Build Into Every Rust Backend From Day One
G
Glory Praise Emmanuel is a software engineer who builds full-stack software solutions and systems. She writes about software engineering, Web3, AI, and career growth in tech. She is passionate about open source, developer education, and community building.

Security is the thing every engineer says they care about, and the first thing that gets cut the moment a deadline tightens. This is the baseline I refuse to skip and why each item actually earns its place.

Let me be upfront about something first. Security isn't glamorous to write about, and it never gets the engagement of a performance post or an architecture debate. But shipping a product with preventable vulnerabilities is worse than shipping slowly, and most security failures aren't sophisticated attacks at all. They're basic mistakes that could have been caught at development time. So here's the baseline I start every backend with.

Defense in depth: each control sits at a different layer, from SQLx and cargo audit at build time, to rate limiting and CSP at the edge, input validation at the API border, Argon2id and JWT handling in the auth layer, and React escaping in the browser

1. SQL injection: eliminated at compile time

In most languages, preventing SQL injection is a discipline. You have to remember to use parameterized queries every single time, in every query, without exception, and it only takes one slip to open a hole.

In Rust with SQLx, it's structural instead. The query macro doesn't allow string interpolation into SQL, so parameterized queries aren't the careful choice, they're the only choice the compiler will let you make. You can't accidentally introduce SQL injection because the language won't let you write it in the first place. That's the right kind of security: enforced by the system rather than dependent on a developer remembering to do the right thing under pressure.

2. Password hashing: Argon2id, not bcrypt

Bcrypt was the right answer for a long time, and it simply isn't anymore.

Argon2id won the Password Hashing Competition in 2015 specifically because it resists GPU-based cracking. It's memory-hard, which means attackers can't cheaply parallelize brute-force attempts the way they can against bcrypt. If you're still defaulting to bcrypt in a new project today, you're making a choice that made sense ten years ago and hasn't aged well. Use Argon2id, configure the memory cost parameter for your hardware, and move on.

3. JWT security: short lifetimes and rotation

Tokens are where a lot of auth security quietly lives or dies, so the defaults here matter.

Access tokens live for 15 minutes. Refresh tokens rotate on every use, so if one is stolen and used by an attacker, the legitimate user's token is invalidated, and you're left with a detectable anomaly rather than a silent compromise. The refresh tokens themselves live in httpOnly cookies, which JavaScript cannot access, and that single decision makes an entire class of XSS-based token theft irrelevant. Short lifetimes don't eliminate token theft, but they tightly limit the blast radius when it happens.

4. XSS: defense in depth

Cross-site scripting attacks inject malicious scripts into your application that then execute in other users' browsers, and the right posture is two independent layers rather than one.

The first layer is Content Security Policy headers, configured at the server level through Tower middleware in Axum. A CSP tells the browser which scripts are allowed to run and from where, so a properly configured policy makes XSS far harder to exploit, even if an attacker does find an injection point. The second layer is your frontend framework. If you're on React, it escapes dynamic content by default, and you have to explicitly opt out with dangerouslySetInnerHTML to lose that protection, which you shouldn't do without a very specific reason. Neither layer is sufficient on its own, but together they make XSS dramatically harder to pull off.

5. Rate limiting: not just for AI

Rate limiting often shows up first around expensive AI endpoints, but it belongs on your auth endpoints too, and with stricter limits.

Something like five failed login attempts per minute per IP before you start throttling is a reasonable floor. This won't stop a sophisticated, patient attacker, and it isn't meant to. It's aimed at credential-stuffing attacks, which are almost entirely automated and volume-dependent, so removing the volume removes the economics that make them worth running. Brute-force auth attacks are boring and preventable, so rate-limit your auth routes.

6. Input validation: at the border, not inside

Every piece of data entering your system should be validated at the entry point, the API layer, before it touches any business logic or database code. Not in the service layer, not in the repository layer, but right at the border.

The reason is that once invalid data slips past the entry point, every downstream function has to defensively handle the possibility of bad input, and that complexity compounds across the whole codebase. So validate early, validate strictly, and reject anything that doesn't match the expected shape before it goes anywhere. In Axum, this falls out naturally: you use extractors that validate and deserialize incoming JSON before your handler runs, so if the shape is wrong, Axum returns a 400 before your code ever executes.

7. Dependency auditing: automated, not manual

Your own code might be spotless, while your dependencies quietly aren't.

cargo audit checks your entire dependency tree against a database of known vulnerabilities, and it belongs in CI, on every pull request and every deployment. You will eventually pull in a dependency with a published CVE, and the only question is whether you hear about it from your own pipeline or from a security researcher who found it first. Automate it once, and it runs forever, for about ten minutes of setup.

In conclusion

None of this is exotic. Almost all of it is well-documented best practice that's been known for years.

The failure mode here was never ignorance; it's prioritization. Features get shipped, security gets deferred, and deferral quietly becomes the default until something breaks. The engineers who build trustworthy systems aren't the ones who know the most obscure security techniques. They're the ones who execute the basics without cutting corners when there's pressure to move fast. Build the baseline, ship it from day one, and never negotiate it away.

What's on your security baseline that I haven't mentioned? 👇