In a world where AI wrappers drop every other minute, Indie Hackers are launching side projects like clockwork, and vibe-coding is the new norm - Security Engineers may have a bit of an edge.
Why? Because they understand something that’s often an afterthought in the rush to ship fast: security.
When the goal is speed, security tends to fall by the wayside.
And don’t even get me started on AI-generated code. It’s often riddled with issues since it follows the same methodology: Implement the use case and make it work. Just scroll tech Twitter and you’ll see plenty of apps getting popped because of it.
As someone who builds side projects myself, I’ve developed a mental checklist over time.
And sure, you can use it as your pre-launch checklist, but I think the real value comes when you start thinking about security proactively during development. You’ll save yourself time and headaches since you’re building securely from the jump.
Now, this isn’t a full-on, 100-point enterprise audit. It isn’t meant to be either. But it’s more than enough to put indie hackers, solo developers, and SaaS founders on solid ground. And honestly, the principles here scale pretty well whether you’re shipping a weekend MVP or your next big thing.
Use it as a guide, not gospel.
Environment Variables
Environment variables are one of the most overlooked security practices by new developers.
To put it bluntly: never hard code sensitive data into your application. This will make it easy for malicious actors to hijack parts of your app or abuse your infrastructure.
This includes (but is not limited to): secrets, credentials, API keys, database URLs, and configuration variables. if you’re unsure whether something belongs in an environment variable, it probably does.
During development, store these values in a .env.local file to store and add it to your .gitignore. Accidentally committing secrets to GitHub is a common way to expose your application to attackers - especially those of you building Open Source Software (OSS).
When you’re ready to go to production, use your hosting provider’s built-in tools to securely store and inject environment variables into your app.
An extra Pro Tip for you: Use separate keys and secrets for your development and production environments!
Authentication & Authorization
Authentication (Authn)
Authentication is how you verify that a user is who they say they are.
Never try to create your own authentication method.
While it may sound like a fun side project, building an authentication component is incredibly complex. It’s best to just run with a plug-and-play hosted solution like:
Supabase
NextAuth
Clerk
These services will handle the hard stuff for you, follow best practices out of the box, and be easy to implement.
And use Multi-Factor Authentication wherever possible. It’s a deterrent to the most common attacks and will save you countless headaches down the road. Thank me later.
Social logins are another great option. Tools like Supabase make it easy to integrate Google, GitHub, and others. These methods reduce friction for account creation and benefit from the security of those big players. Win-win.
Finally, manage your sessions properly:
Use session timeouts
Regenerate session IDs on login
Invalidate sessions on logout
These methods will protect your users from session hijacking and help you keep control over active sessions.
Authorization (Authz)
Authorization determines what users are allowed to do once they’re logged in.
This part takes some proper planning - something AI isn’t going to do well for you quite yet. From day one, start to think about how you’ll implement Role-Based Access Control (RBAC) to enforce who can access what.
Stick to the Principle of Least Privilege: Give users only the permissions needed to use the app, and nothing more.
In layman's terms - if your users don’t need admin-level powers, don’t give it to them. This not only applies to user roles in the app, but also how you scope API Keys for integrated services.
- Today’s Sponsor -
Strop struggling with complex queries. Selecty is a database-agnostic query assistant that integrates seamlessly into your workflow. Easily generate, explain, optimize, format, and validate queries - all in one place. Write better queries, faster than ever, and make your data work for you. Check it out!
Input Validation
One of the most common design flaws for new developers is poor input validation.
This is the process of checking all user-supplied inputs to make sure they match the expected format and content before your app processes them.
Proper input validation, sanitization, and verification can prevent some of the most dangerous vulnerabilities out there. Think SQL Injection (SQLi), Cross-Site Scripting (XSS), Command Injection, Buffer Overflows, and more…
Client Side
User input is always dangerous.
Never trust anything passing through your front-end. Before rendering input in the DOM or sending to your back end, sanitize by removing or encoding any potentially malicious characters.
Use tools like DOMPurify to clean up user input safely.
You can also leverage output encoding, which is the process of encoding data before returning it to the client.
In short: Never trust. Treat everything as plain text, never as executable code.
Server Side
A major (and unfortunately common) mistake is accepting user input and passing it directly into your back end logic.
The biggest “no-no” being concatenating user input directly into SQL statements, opening up your app to SQLi.
Always use prepared SQL statements or parameterized queries. This will safely bind variables and prevent malicious input from altering your queries.
It’s also important to protect against Insecure Direct Object References (IDOR) - which is when users are able to access or manipulate data they shouldn’t.
A good rule of thumb: Users should only access data they’ve created, or that’s explicitly tied to them through a team/organization. Always verify that the user requesting data has permission to access it.
Error Handling, Rate Limiting, & Logging
Error Handling
There are going to be plenty of times during development of your product that you’ll want to return an error message to the user.
While this may seem like a benign way to be helpful to users, you want to be sure to keep those messages generic.
Verbose error messages can leak information about your app’s internals, which is a gold mine for attackers trying to figure out how your app is functioning on the back end.
Consider a login flow where an attacker is attempting to brute force a user account:
✅ Good: “Wrong username/password. Try Again.”
❌ Bad: “Error - The password does not match the given userId in the database. Try again.”
That second error message might help while debugging, but it can also hint to the attacker they may have a valid userId.
Pair verbose error messages with no rate limiting, and now you’ve got a serious problem…
Rate Limiting
Rate limiting is more than just good hygiene, it’s also a great first line of defense.
This simple function can protect your app from:
Brute force attacks
Bot abuse
Denial of Service (DoS) Attacks
Misuse of endpoints
Start simple - add request throttling to your sensitive endpoints then expand as needed.
It’s better to slow someone down than to let them hammer your app freely.
Logging
Logging is essential when building - it’s how you trace bugs, monitor behaviors, and get insights into how your app is being used.
But when you ship to production, you need to approach logging methodically.
Here’s a few simple rules to follow:
Don’t log secrets, API keys, tokens, or anything from your environment variables
Avoid leaving unnecessary console.log statements in production.
Keep production logs less verbose - you can always add more over time.
Use logs to get a high-level sense of what’s happening, not to capture everything.
Logging architecture will scale with your app, so don’t compare what you have to security audit logs you would find in enterprise applications.
Your goal is to balance visibility with security. Use logs to help you catch red flags, not become one.
API Layer
Don’t make the same mistake so many first-time developers do.
Always have an API layer.
It acts as a separation between your front end and application logic, keeping the sensitive stuff off the client side and hidden behind secure endpoints.
Think of it as security by abstraction.
Instead of writing logic directly to the front end, your app becomes a consumer of your API - contributing to security by limiting visibility to malicious actors about what’s happening behind the scenes.
A well-structured API gives you control, security, and scalability. In order to achieve this, you need to pair it with some practices we’ve already touched on:
Rate Limiting - Throttle requests by IP, userId, and session to prevent spam and abuse.
Authentication - Only let legitimate users access your API.
Authorization - Make sure users can only access what they’re allowed to.
Input Validation - Stop malicious payloads before they reach your backend logic.
You can also take things one step further by leveraging an API gateway like Kong. A gateway acts as a secure, centralized entry point for all API traffic and can help route requests to backend services, enforce Authn and Authz, manage traffic, monitor usage, apply rate limits, and help scale over time.
Bottom Line: Don’t view an API layer as optional - it’s foundational.
💬 Do you have a mental checklist of your own before you push to production? Drop a comment below and let me know what’s on yours!
Dependencies
While your code might be clean, if one of your dependencies is vulnerable, so are you.
That’s the risk you must accept when using third-party integrations. And in today’s world of fast-moving OSS, it’s not an if, but a when.
Any dependencies you use need to be kept up to date. Outdated packages are one of the easiest attack vectors.
Use your package manager to regularly audit and flag outdated or vulnerable modules:
Npm audit && npm update
Yarn audit && yarn upgrade
Pip-audit && pip install --upgrade
If you’re running a larger operation, you can also leverage tools like SCA, DAST, and SAST. These tools can give you visibility into vulnerabilities, but be prepared for an onslaught of noise.
When in doubt, update.
Data Storage & Encryption
As data moves through your app and sits in your database, your job is to make sure it stays safe from interception or unauthorized access.
There are two key phases where encryption is essential:
For In-Transit, use TLS (Transport Layer Security) to encrypt data as it moves across your network. TLS uses digital certificates to protect sensitive data from man-in-the-middle attacks.
While At-Rest, use AES (Advanced Encryption Standard) to encrypt stored data. AES is a symmetric-key algorithm that’s fast, secure, and trusted across industries.
Best practices for your data include:
Rotating your keys regularly.
Using the Principle of Least Privilege for access to data.
Reserving your database root account for emergencies only.
Data security is the number one way to build credibility and protect your users.
Headers and Configs
HTTP Headers are one of the easiest ways to tighten your app’s defenses.
Adding a Content Security Policy header to your request can prevent attacks like XSS, clickjacking, and data injection attacks by restricting what sources are allowed to load.
All you have to do is identify your application’s assets, and define a CSP policy in the HTTP response header like this:
Content-Security-Policy: default-src 'self'; script-src 'self' cdn.example.com; object-src 'none';
This restricts all default resources to be loaded from the same origin, scripts to be loaded from your site and a trusted CDN, and disables loading of plugins or embedded objects entirely.
Pro-Tip: use the Report-Only mode to test new policies to not breaking functionality:
Content-Security-Policy-Report-Only: <your-policy>
You can also add a CSRF header along with a SameSite attribute on cookies to prevent CSRF attacks and prevent your users from being tricked into performing unwanted actions.
Use CSRF tokens in your headers to verify legitimate requests and set SameSite attributes on cookies to control when cookies are sent with cross-site requests.
Headers are often ignored, but a few well-placed lines can go a long way to hardening your security posture.
Safe to Ship
Hopefully this can help you get your next side project or micro-SaaS to production in a safe, secure way.
Just remember: security isn’t a one-size-fits-all.
A lot of decisions will depend on the application context and its threat model, so make sure to do your research and stay mindful of how you’re protecting your users.
Want to dive deeper?
Check out these quick, WWWWWH analyses on some of the vulnerabilities mentioned in this article:
Securely Yours,
Ryan G. Cox
Just a heads up, The Cybersec Cafe's got a pretty cool weekly cadence.
Every week, expect to dive into the hacker’s mindset in our Methodology Walkthroughs or explore Deep Dive articles on various cybersecurity topics.
. . .
Oh, and if you want even more content and updates, hop over to Ryan G. Cox on Twitter/X or my Website. Can't wait to keep sharing and learning together!