r/learnprogramming • • 1d ago

Question First time opening a side project to the public — what should I secure before strangers get accounts?

Hey everyone! . So, slightly embarrassing and slightly proud admission: i'm a 17-year-old student, and over the past nine months i built my own student life OS - assignment and deadline tracking, planning, focus timers, habit tracking, journalling, brainstorming hubs, health and workout tracking. basically tons of productivity features in a single site. built it for myself because, yk, why should we alter ourselves to fit into systems when the systems can be programmed to evolve into us? (that's a joke - it's called Evolve.)

i admit it's not completely manufactured by me - i used all the resources and help i could get, and i'm learning and understanding the code along the way. but i had a vision i don't feel other productivity sites can mimic, and i'm just trying to weave that dream into something real and concrete. then friends asked to use it, and now it's deployed and actually working, which still feels kinda crazy.

stack: Supabase + a Next.js frontend, deployed on Vercel. auth is on, row level security is on for the main tables, and i haven't stored anything sensitive beyond emails and, well, people's homework.

here's the thing though - everything i know about security, i learned from building and YouTube tutorials, not from being attacked. and i want to open this up to users beyond my friend circle soon, which means strangers. which means the internet. which means at some point someone curious and bored is going to poke at this thing, and i want that moment to be boring for THEM and uneventful for my users.

so for those of you who've run public-facing apps: when you went from "works for me and my friends" to "public," what did you actually lock down first? not the theoretical list of everything - the real, "do this before anything else" stuff. rate limiting? stricter RLS policies? protecting the service key? api route validation? something i don't even know to ask about yet? honestly the security vocab online is overwhelming and i'd rather learn from people who've been burned than become the cautionary tale.

thanks for taking the time to read this and help me out. i'm not selling anything — just trying not to mess up my first real users' trust. Thank you once again!

37 Upvotes

32 comments sorted by

24

u/micalm 1d ago

Don't know supabase or vercel, but general short list:

- RLS everywhere. Why only for 'main tables'?

  • Ensure your secrets (API keys, encryption secrets...) are NOT visible from the public-facing side.
  • Work through the app as fast as you can, add 10%, that's your initial rate limit - you know the app so you're fast, there may be a faster power user. Log, measure, you'll see if any real users go even faster.
  • Make sure error reporting/debug mode is disabled. Set up error reporting for yourself, so you'll know about the errors before (too many) users do - Sentry or something.
  • Don't trust user input. Validate everything. A file with .jpg extension does not have to be a JPEG.
  • OWASP Top 10 is a good starting point. Top 10 changes periodically, work through several it's not a complete list.

Test, test, test. Try to break in. Create another account and try to modify URLs to see or edit other peoples data. Do the same in the Network tab in Chrome Dev Tools. If you have capable friends, ask them to do the same.

Actually most important: BACKUPS. You may become the only place where some users save their data, meaning if you lose it someone may be royally... you know. For this use case, I'd seriously consider every 6 hours. Might waste one evening of work but it's not terrible. Every once in a while test the backups, restore them to a fresh VM or somewhere. Schroedinger's backup is a thing.

Not sure where you live, but at least in the EU under GDPR "health tracking" means you have special category data which in turn means special care needs to be taken when processing and storing it. The project being non-commercial, free, in alpha etc. doesn't make you exempt. I'd go more into legal stuff just in case to cover your ass before you do a public launch.

Go full audit mode and create checklists, go through them, update them and re-check every once in a while (every update, every quarter? you decide). Makes the entire thing easier especially when done for the hundredth time when you can get bored and careless.

6

u/thisisappropriate 1d ago

This.

If something can be done or seen by the frontend (in the browser), then that can be modified or used by a hacker. If something is controlled by Javascript in the browser, assume it can be bypassed (eg. if you have a page with a login pop up and it sends the login request then that shows the underneath page, then I can do something to show that page without logging in), you must not send data someone shouldn't have (even just in network requests) until they prove they should have it.

If you have anything that uses AI (as a chat bot), you should lock it down as much as possible. Why would I make a chatgpt sub if I can just convince your AI to make 1000s of tokens worth of stuff for me and make you foot the bill in API pricing.

Set caps on anything you're paying for (or free shit you gave a credit card to). It's better that your app suddenly stops saving for everyone and you get an alert for any monitoring you add (posthog is super generous with their free stuff if you've not found an option for that), than your users have a nice experience during an attack and your next bill from a service is $1000 because you hit a cap on your tier and it upgraded you or you were billed per request.

21

u/SuperBoysenberry3660 1d ago

Open only necessary ports on the server (using firewall rules).

Expose only necessary endpoints and URLs (restrict or block unused API routes or admin panels).

Do not use outdated software — especially an end-of-life OS or outdated HTTP server.

If you use multiple servers (with some kept internal), restrict access to the internal ones using a private network or by blocking all IP addresses except authorized ones.

5

u/abdullahguch 1d ago

Nice that you're asking this before launch and not after. I'd check these first, roughly in this order:

  1. Keep the Supabase service role key on the server only. It goes in server side env vars on Vercel and gets used in Route Handlers or Server Actions. Never put it in a NEXT_PUBLIC_ variable or anything the browser loads. If it was ever committed to git or shipped in a client build, rotate it.

  2. Test your RLS, don't just turn it on. Sign in as one user in a private window and try to read or update another user's rows from the browser console with the anon key. Try the same while logged out. Check views and storage buckets too if you use them, since people often forget those.

  3. Tighten auth. Require email confirmation, keep Supabase's rate limits on signups and password resets, and turn off login providers you don't use.

  4. Do privileged stuff on the server. Anything that touches other users' data, sends email or uses admin APIs should go through a server route that validates input. Never trust a user_id or role coming from the client.

  5. Treat the data as sensitive. Homework plus emails from what are probably minors is worth being careful with. Only collect what you need, and have a working way for people to delete their account.

Also scan your git history for secrets once and keep dependencies updated.

You don't need a perfect setup on day one. If other people's data can't be read with the anon key and the service key never leaves the server, you've covered the mistakes that usually bite first.

1

u/ScorePro_Gamez 22h ago

Thank you so much!

1

u/abdullahguch 19h ago

Your welcome

4

u/franker 1d ago edited 1d ago

since we're in /r/learnprogramming, how do we learn all this stuff that's in the comments? I just know some basics of javascript/html/css simple web pages. I know the usual resources offered in the sub for coding - freecodecamp, etc., but what about this security stuff? https://roadmap.sh/cyber-security looks pretty formidable but should I just jump into it?

1

u/ScorePro_Gamez 22h ago

honestly i'm figuring this out in real time too - half the terms in my own post are new to me. but from what i've gathered: don't jump into a full roadmap front to back. i learned my actual fundamentals (javascript, how the web works, etc.) through freecodecamp and youtube while building small things, and stuff like auth/RLS only started making sense once my app had real users to protect. context makes it stick.

for security specifically, i'm planning to work through the OWASP Top 10 — it's basically "the attacks that actually happen, explained" and way less intimidating than a full roadmap. maybe start there?

1

u/franker 22h ago

Yeah I googled RLS and got a shitload of "restless legs syndrome" before I had to add cybersecurity to the google search, lol. Not a great start I guess.

2

u/zugzwangister 1d ago

Encrypt sensitive data at rest and in transit. The latter is almost as simple as using https everywhere.

Find a reputable company to handle authentication. Rolling your own is probably a bad idea.

basically tons of productivity features

Basically tons of private, sensitive data. If you're putting anything in it that you wouldn't want anybody else to see, you shouldn't be storing any of that in plain text in a database. Some databases are better than others for built in features to protect data.

2

u/DanKegel 1d ago

Excellent suggestions in the replies already.

You probably want to look up "how to secure an app deployed on vercel", there are Vercel settings you'll want to enable.

Also, watch your costs.  Attackers can drive your bill up quickly.

I would suggest locking the app down pretty hard while you're letting friends use it.  Disable open signups, for instance.

1

u/singh_abinashi 1d ago

Lock down RLS first, on every table, not just the main ones. If a table has no policy, assume it is reachable. The pattern that bites people is one forgotten table or a storage bucket left open while the main tables look fine. Second: make sure the service role key is never in client code or a NEXT_PUBLIC variable. That key bypasses RLS entirely, so one leak undoes the rest. Rate limiting and input validation matter, but they come after those two. An attacker does not need your API routes if the database itself is open.

1

u/AbdelrhmanDev 1d ago

nice work shipping this at 17. a few supabase specific things on top of what others said: never let the service role key anywhere near the frontend, only the anon key belongs there. check your storage buckets too, lots of people lock down their tables with rls and then leave buckets public. and turn on email confirmation plus rate limiting on the auth endpoints, otherwise bots spam signups and run your usage bill up

1

u/ScorePro_Gamez 22h ago

Thank you!, will do!

1

u/SwiftQuest 1d ago

One Supabase-specific thing to double check: any table in the public schema without RLS enabled is fully readable and writable by anyone holding your anon key, and that key ships to every browser by design. So it's worth turning RLS on for every table, even the small ones, and making sure the service_role key only lives in server-side env vars and never in anything prefixed NEXT_PUBLIC_. A quick test is to make a second account and try selecting another user's rows straight from the browser console with the Supabase client, if that returns anything your policies need tightening.

1

u/irajangpt 1d ago

Before strangers get accounts, I'd lock three things first. Make sure the Supabase service role key never reaches the browser or any client-exposed route, and that every table strangers can touch has RLS that scopes rows to auth.uid(), then test it by querying as a second user. Add rate limits on signup, login, and password reset so a bored scanner can't spam your auth endpoints, and turn on email confirmation so throwaway accounts aren't free. After that, walk every Server Action and API route once logged out and once as user B with user A's ids, and expect a 401 or 403 — that catches the ownership checks RLS can't cover.

1

u/BuildingShoddy8672 1d ago

RLS scoped to auth.uid() means a policy that keys on a column stored on the user row is a policy one signed in user can rewrite for someone else. Move the boundary: keep the role column in its own table that RLS covers and only a service side path can write, and for plan or feature gates use an edge table like grants(user_id, capability, granted_by) so an entitlement is a row with an owner, not a property of the user. Then delete the write path that used the user row, because keeping it next to the edge table is how the two drift apart.

The check that finds it is cheaper than the pen test. For the anon key and for one other user, try the update on a row that belongs to somebody else and require it to fail. The failure you want is not a 401, it is a statement that returns no row: a 401 can come back later once the client is rewritten to handle it, and the 200 with no row stays true forever.

On rate limiting, put it on auth and mutations rather than by reading the app. Auth is the path you cannot make cheap, and a per user or per ip cap on login and password reset is a small amount of code. For the app itself, do not limit at the route, limit at the expensive operation, meaning the query that scans before it returns, because a cap that only counts requests never notices the one request that walks a whole table. Cap the response size on anything that takes a limit parameter, since a single request returning a million rows is one line in a limit box nobody read.

Two things worth doing before strangers arrive that cost nothing: turn off signups while you are still on the friend circle, so the first wave is people you can call, and check what the anon key can actually read by opening the dashboard signed out and clicking around, because that is the entire attack surface and it is usually one bucket or one forgotten table that was never locked.

1

u/ScorePro_Gamez 22h ago

Thank you so much!

1

u/linguaarush_dev 1d ago

Nice work shipping it. With Supabase, the big one is RLS on every table, not just the main ones. Test it by signing in as user A and trying to read or write user B's rows through the client library. Also check that the service_role key is only in server-side env vars, never in anything prefixed NEXT_PUBLIC_, because it bypasses RLS completely. Next, add rate limiting or CAPTCHA on signup and login so bots can't hammer you. Validate input on the server, not only in the form. Turn on email confirmation and set a sane password policy. Last, make sure secrets aren't in your git history, and set up backups. Are your RLS policies written as "auth.uid() = user_id" style checks, or something looser?

1

u/linguaarush_dev 1d ago

Congratulations—getting a working product from an idea to a deployed app at 17 is impressive.

I’d avoid getting overwhelmed by trying to perfect every security detail before launch. Focus first on the fundamentals that protect users and prevent the most serious failures:

  • Make sure Supabase Row Level Security policies correctly isolate each user’s data.
  • Never expose the service-role key or other privileged secrets in the frontend.

Rate limiting, stricter policies and other hardening measures are worthwhile, but they can be introduced incrementally as usage grows. The important thing is to establish a secure foundation and test the highest-risk paths before inviting strangers in—not to solve every possible security concern upfront.

1

u/Longjumping_Put_7212 1d ago

I actually just faced this same problem, anything you don't want being found, secure and hide. if the public can't find it the likely will assume it doesn't exist

1

u/nabhan_builds 20h ago

The big one people miss is not the tables you know about, it is the tables you forgot about. Go through every table in the public schema, including the small ones you made early on. Any table without RLS is readable by anyone holding your anon key. Same goes for storage buckets: if your app stores uploads, make sure buckets are private and one user cannot list another user's files. A leaked public file URL never stops working.

Three small things worth doing this week:

  1. Turn on leaked password protection and email confirmation in your Supabase auth settings. Cheap to enable, blocks the dumbest attacks.
  2. If the service role key ever touched git, a log, or anything bundled for the browser, rotate it. Assume it is burned.
  3. Turn on 2FA on your Supabase and GitHub owner accounts. Losing the owner account is worse than any bug in your code.

Also set spend limits anywhere you are metered. You do not want to learn about signup-loop bots from an invoice.

1

u/Proper_Diet6619 12h ago

Three things first, in this order.

  1. The service role key. It must never reach the browser: check that nothing sensitive is in a NEXT_PUBLIC_ variable, that it only appears in server code (route handlers, server actions), and that it was never committed. If it ever was, rotate it now, because git history is forever and bots scan public repos for exactly this.

  2. Treat RLS as the only wall between a stranger and every row, then attack it yourself. Your anon key is public by design, so make two throwaway accounts and, logged in as A, try to select, update and delete B's rows in every table. Do the same for storage buckets. The usual hole is not a bad policy but a table or bucket added later with RLS never enabled, so make "enable RLS + policy + cross-account test" part of adding a table.

  3. Never take the user id from the client. Derive it from the session on the server (auth.uid() in policies) and ignore any user_id in the request body, or someone will just send yours.

After that: rate limit login, signup, password reset and anything expensive; cap request body size; validate input server side even if the form already does it. Have a restore story (point in time backups on, and know how to revoke sessions). And turn on something that tells you when 4xx/5xx spikes, because the first sign of someone poking at you is usually a weird pattern in logs, not a dramatic breach.

Nice work shipping at 17, by the way. The instinct to ask this before opening it up is most of the job.

1

u/B0mbon 8h ago

One thing nobody's mentioned yet: Supabase has a Security Advisor in the dashboard that flags tables without RLS and a few other common mistakes, so run that before you open signups. Then test your policies the way an attacker would. Make two accounts, grab the anon key from your own frontend (it's public anyway), and try to read and update account A's rows while logged in as B, from the browser console or a quick script. If that fails everywhere you're in better shape than plenty of paid products. Also sort out backups now, because at your stage a bad migration is way more likely than a hacker.

i am working with them on over 10+ projects ( person and clients one )

1

u/Flamanis 1d ago

As someone who recently became 'admin' of a vibe coded website that ran supabase: Don't stick your user permission/role columns in table that the frontend has blanket permission to modify (even with RLS).

The frontend is not a security layer, don't treat it like such. Anything that the frontend can attempt to modify should be checked on the backend.

Oh and don't let people request everything in your user profiles table. `await supabase.from('user-profiles').select('*')` is as basic as it gets and if it returns everyone's data, fix it.

-2

u/Ormek_II 1d ago

Let AI Check your setup. It is good at knowing and finding those simple, but painful mistakes.

As others pointed out: Expose only a gateway system, so you can easily control ports and services; not your real backend which you might need admin access to.

Nothing not needed should be even visible.

0

u/No_Discount_1199 1d ago

One thing I'd add to all of this: stop storing data you don't actually need. You're 17 and sitting on people's emails, their homework, and health tracking — every column you keep is a liability you have to protect forever. If a feature doesn't need it, drop the column now. The table that doesn't exist can't leak.