VIBE-LEAK: 16.326 Ανοιχτές Βάσεις
Στις 25 Σεπτεμβρίου 2026 η UpGuard δημοσίευσε τη μεγαλύτερη μελέτη του είδους: από ~300.000 domains με Supabase, τα 16.326 είχαν πίνακες που διαβάζει όλο το internet — χωρίς login, χωρίς κλειδί, με ένα URL. Πάνω από τα μισά έδειχναν προσωπικά δεδομένα. Η αιτία δεν είναι hack. Είναι το default που ζει σε λάθος μέρος — και τα AI coding tools που παίρνουν πάντα τον δρόμο χωρίς αυτό. Αυτό είναι το πλήρες τεχνικό dossier: κλίμακα, μηχανισμός, lineage, και το audit που κλείνει την τρύπα σε 15 λεπτά.
- Βάσεις με readable tables
- 16.326 από ~300.000 domains · floor, όχι ταβάνι
- Με δείκτες PII
- >50% ονόματα, emails, τηλέφωνα, διευθύνσεις
- Με passwords/tokens
- μειοψηφία συχνά plaintext · σπάνια κάρτες
- Αποτίμηση Supabase
- $10B Ιούνιος 2026 · vibe-coding backend
- Enforcement νέων defaults
- 30/10 δεν αγγίζει existing tables
01 // Η κλίμακα: τι μέτρησε η UpGuard
Η μελέτη «Everything Everywhere» (UpGuard Research, Greg Pollock, 25/9/2026) είναι η μεγαλύτερη του είδους — και η μεθοδολογία της έχει μία λεπτομέρεια που την κάνει χειρότερη απ' όσο ακούγεται. Οι ερευνητές εντόπισαν domains με Supabase (BuiltWith + Chrome UX Report + API keys σε δημόσια JavaScript) και μετά ζήτησαν από κάθε βάση έναν πίνακα με συγκεκριμένο όνομα: users. Βάση που διαρρέει customers ή orders αλλά δεν έχει users δεν μετρήθηκε ποτέ. Το 16.326 είναι κατώφλι.
Αντί να διαβάσουν κάθε γραμμή (16 χιλιάδες βάσεις × άπειρα rows), ανέλυσαν schemas: ονόματα πινάκων και στηλών σαν δείκτες περιεχομένου. Όπου τα metadata έδειχναν σοβαρή έκθεση, το επαλήθευσαν δειγματοληπτικά — και ειδοποίησαν τους ιδιοκτήτες. Σωστή δεοντολογία, σπάνια σε τέτοια scale.
Η gallery των θυμάτων
43.000 ονόματα+emails, 665 employee records με 4.560 corporate emails και push tokens. Ποιος παρκάρει πού, πότε, με ποιο αμάξι — δημόσια.
Ονόματα, emails, τηλέφωνα, ημερομηνίες γέννησης. Άνθρωποι σε ευάλωτη μεταναστευτική διαδικασία, με κωδικούς σε καθαρό κείμενο.
Προσωπικά στοιχεία και φυσικές διευθύνσεις — μέσα και emergency housing ευάλωτων μεταναστών. Κρατική υπόθεση, ανοιχτή στο web.
Virtual SIM υποδομή με μηνύματα — μέσα και one-time passcodes. Υποδομή που κανονικά υπηρετεί bulk account-creation fraud, τώρα διάφανη.
Ιδιωτικές συνομιλίες χρηστών-performers, πλήρως αναγνώσιμες. Το είδος έκθεσης που δεν «μαζεύεται» ποτέ πίσω.
Γεωγραφία και κλάδοι, όπως τα χαρτογράφησε η έκθεση: παγκόσμιο φαινόμενο, αλλά η Ευρώπη τα πάει καλύτερα (οι νόμοι προστασίας δεδομένων σπρώχνουν καλύτερες πρακτικές) και οι αναπτυσσόμενες περιοχές χειρότερα. Χειρότεροι κλάδοι: e-commerce και εστιατόρια (PII + payments)· πιο πιθανά να διαρρέουν passwords: μη αδειοδοτημένα στοιχηματικά sites. Το αν πουλάς σε καταναλωτές ή επιχειρήσεις δεν έκανε καμία διαφορά.
Το πόρισμα της UpGuard σε μία πρόταση: οι διαρροές είναι το πολλαπλασιαστικό γινόμενο της ευκολίας λανθασμένης ρύθμισης μιας τεχνολογίας επί το μέγεθος της βάσης χρηστών της. Το Supabase έκανε τη βάση πανεύκολη· τα AI tools έκαναν τους χρήστες εκατομμύρια. Το γινόμενο είναι 16.326 — τουλάχιστον.
02 // Ο μηχανισμός: το default που ζει σε λάθος μέρος
Το Supabase εκθέτει κάθε πίνακα σαν REST endpoint: yourproject.supabase.co/rest/v1/your_table. Γι αυτό το AI χτίζει app τόσο γρήγορα — δεν γράφει backend, ο browser μιλάει κατευθείαν στη βάση. Η γραμμή άμυνας είναι μία και μοναδική: Row Level Security policies σε κάθε πίνακα.
Δύο defaults, δύο κόσμοι
Φτιάχνεις πίνακα στο dashboard (Table Editor): το RLS ανάβει μόνο του. Τον φτιάχνεις με SQL, migration, CLI, psql — ή τον φτιάχνει agent: RLS off. Η λεπτομέρεια που τα εξηγεί όλα (reptile.haus, 26/9): το default είναι pre-ticked πεδίο σε React φόρμα (isRLSEnabled: true) — το table-creation API δεν έχει καν παράμετρο RLS. Δημιουργία και προστασία είναι δύο ξεχωριστές κλήσεις· ο agent κάνει πάντα μόνο την πρώτη. Το ασφαλές default δεν ζει στη βάση, ούτε στο API, ούτε στην πλατφόρμα. Ζει στην αρχική κατάσταση ενός component. Κάθε client που δεν το renderάρει κληρονομεί το Postgres default: ασφάλεια off.
Το anon key δεν είναι μυστικό — και αυτό είναι το point
Κάθε Supabase app κουβαλάει το publishable/anon key στο bundle — by design δημόσιο, η Supabase το λέει ρητά «safe to expose». Το κλειδί ταυτοποιεί το project, δεν εξουσιοδοτεί τον καλούντα. Η εξουσιοδότηση είναι το RLS. Με RLS off, το «δημόσιο» κλειδί είναι de facto master key:
// το key είναι ήδη στο bundle — το παίρνεις από το Network tab const r = await fetch( 'https://xyz.supabase.co/rest/v1/users?select=*', { headers: { apikey: 'PUBLIC_ANON_KEY', Authorization: 'Bearer PUBLIC_ANON_KEY' } } ); console.log(await r.json()); // ← όλοι οι users, αν το RLS είναι off
Και το αντίστροφο λάθος είναι καταστροφή από την άλλη μεριά: το service_role key παρακάμπτει κάθε RLS και ζει μόνο σε backend. Αν το βρεις σε client bundle, δεν έχεις «θέμα» — έχεις διαρροή: rotate αμέσως, μεταφορά κλήσης σε server function. Κανόνας: grep -r service_role src/ — επιτρέπεται να εμφανίζεται μόνο σε server paths.
Τρία grants, μία παγίδα
Από τα ίδια τα docs της Supabase — ο πίνακας που πρέπει να ξέρεις απέξω:
| Role | Παίρνει αυτόματα | Τι πρέπει να κρατήσει |
|---|---|---|
anon | select, insert, update, delete | Μόνο ό,τι διαβάζει signed-out επισκέπτης |
authenticated | select, insert, update, delete | Μόνο operations που εκθέτει το app |
service_role | select, insert, update, delete | Πλήρης πρόσβαση · bypass RLS · server-side μόνο |
Προσοχή στη λεπτομέρεια-φωτιά: τα policies δεν ανακαλούν grants. Πίνακας «προστατευμένος» μόνο με policies χαρίζει ακόμα insert στον anon αν δεν έκοψες ποτέ το grant. Και τα views, by default security definer (τρέχουν σαν τον owner postgres), παρακάμπτουν το RLS του υποκείμενου πίνακα — θέλουν security_invoker = true (Postgres 15+) αλλιώς διαρρέουν ό,τι «προστατεύεις».
Η φράση-κλειδί όλου του dossier: «destroying data stops the machine — exposing data prints a note». Το MCP server της Supabase σταματάει το destructive SQL μέχρι human confirm, αλλά για RLS off απλώς συμβουλεύει (string στο list_tables, do-not-auto-apply — γιατί το άναμμα χωρίς policy κλειδώνει την εφαρμογή). Καταστροφή = gate. Διαρροή = σημείωση, και μόνο αν ρωτήσεις. Αυτή είναι η default στάση σχεδόν κάθε agent integration.
03 // Lineage: από τα 170 στα 16.326
Το Σεπτέμβρη δεν ανακαλύφθηκε κάτι καινούργιο — μετρήθηκε σε κλίμακα κάτι γνωστό. Το χρονικό:
- ΜΑΡΤΙΟΣ 2025Ο Matt Turner σκανάρει 1.645 Lovable showcase apps → 170 ελαττωματικές, 303 endpoints. Μία app: 18.697 records, 14.928 emails, 4.538 φοιτητικοί λογαριασμοί (K-12 + Berkeley/Davis), 870 full PII — και μία με ανεστραμμένη λογική (μπλοκάρει logged-in, αφήνει anonymous).
- 29–30 ΜΑΪΟΥ 2025Η NIST δημοσιεύει το CVE-2025-48757 (CVSS 9.3, CWE-863). Η Lovable το αμφισβητεί: ευθύνη του πελάτη. Το scan, όχι.
- 2025–2026Οι μικρές μελέτες στοιβάζονται: Modern Pentest (107 YC startups, 28% εκτεθειμένα) · Symbiotic (1.072 apps → 39) · Escape (175 βάσεις / 1.400 apps) · Red Access (380k URLs → 5k προσβάσιμες → 2k διαρροές).
- ΦΕΒΡΟΥΑΡΙΟΣ 2026Wiz: το Moltbook (social για AI agents) — μηδέν RLS παντού, 35.000 emails + 1,5M API tokens (OpenAI, Anthropic).
- 25/9/2026UpGuard: 16.326 — η κλίμακα ×100. TechCrunch, CISO Harmer («secure by default»), changelogCantillon: opt-out 28/4 → default νέων projects 30/5 → enforcement 30/10.
| Μελέτη | Δείγμα | Εύρημα |
|---|---|---|
| Turner (3/2025) | 1.645 Lovable apps | 170 ελαττωματικές · 303 endpoints → CVE-2025-48757 |
| Modern Pentest | 107 YC startups | 28% εκθέτουν PII |
| Symbiotic | 1.072 vibe-coded apps | 39 readable με public key |
| Escape | ~1.400 apps | 175 βάσεις με PII |
| Red Access | 380.000 URLs | 5.000 προσβάσιμες · 2.000 διαρροές |
| Wiz (2/2026) | Moltbook | 35k emails + 1,5M tokens · RLS πουθενά |
| UpGuard (9/2026) | ~300.000 domains | 16.326 · >50% PII |
04 // Το fix-loop: πώς το AI «διορθώνει» την ασφάλεια
Το πιο δηλητηριώδες εύρημα δεν είναι τεχνικό — είναι συμπεριφορικό. Η Palo Alto Unit 42 το λέει «fix loop»: ο agent γράφει query, η Postgres απαντά 42501 insufficient_privilege (το RLS κάνει σωστά τη δουλειά του), και ο agent το «φτιάχνει» ρίχνοντας το RLS. Το query δουλεύει. Ο πίνακας είναι ορθάνοιχτος. Το Carnegie Mellon το ποσοτικοποιεί: 61% του AI κώδικα λειτουργικά σωστό, μόνο 10,5% ασφαλές. Και 34% των vibe apps βγαίνουν χωρίς ούτε CSP/HSTS/clickjacking headers.
Η θεατρική ασφάλεια: 5 μοτίβα που περνάνε το «RLS on»
- Το ξεχασμένο related table. RLS στο
todos, τίποτα σταtodo_tags / comments / attachments— η διαρροή περνάει από δίπλα. USING (true). Policy που ισχύει για όλους = καθόλου policy. Το AI το γεννάει όταν δεν καταλαβαίνει το ownership.- Έλεγχος μόνο
auth.role() = 'authenticated'. Αποδεικνύει login, όχι ιδιοκτησία — κάθε user βλέπει κάθε row. - UPDATE χωρίς
WITH CHECK. Ο user αλλάζει τοuser_idτης δικής του γραμμής και γίνεται «κάποιος άλλος» — κλασικό IDOR. - Views χωρίς
security_invoker+ service_role στο bundle. Το πρώτο παρακάμπτει σιωπηλά, το δεύτερο φωναχτά.
ALTER TABLE notes ENABLE ROW LEVEL SECURITY; CREATE POLICY "read own" ON notes FOR SELECT TO authenticated USING ((select auth.uid()) = user_id); CREATE POLICY "insert own" ON notes FOR INSERT TO authenticated WITH CHECK ((select auth.uid()) = user_id); CREATE POLICY "update own" ON notes FOR UPDATE TO authenticated USING ((select auth.uid()) = user_id) WITH CHECK ((select auth.uid()) = user_id); CREATE POLICY "delete own" ON notes FOR DELETE TO authenticated USING ((select auth.uid()) = user_id);
Δύο λεπτομέρειες που αξίζουν: το (select auth.uid()) με παρένθεση αφήνει τον planner να το υπολογίσει μία φορά ανά query (performance σε μεγάλους πίνακες), και το TO authenticated κρατάει τον anon εντελώς έξω — ακριβώς ό,τι θέλεις για ιδιωτικά δεδομένα.
05 // LAB 1 · RLS Exposure Simulator
Προσομοιωμένο Supabase endpoint — συνθετικά δεδομένα, τίποτα αληθινό. Διάλεξε πίνακα, ρόλο και κατάσταση RLS, πάτα RUN και δες τι επιστρέφει το API. Αυτό που βλέπεις είναι ακριβώς αυτό που είδε η UpGuard, σε μικρογραφία.
06 // LAB 2 · 12-Config Matrix
Αναπαράσταση του τεστ 12 διαμορφώσεων (creativeainews): 4 διαρρέουν, 2 επιτρέπουν υπέρβαση, 6 κρατάνε. Πάτα κάθε config και δες την ετυμηγορία — μάντεψε πρώτα, μετά επαλήθευσε.
12 κουμπιά, 12 μοίρες του ίδιου πίνακα. Οι μισές και πάνω «δουλεύουν» — λίγες προστατεύουν.
07 // Το 15λεπτο audit (και τι δεν φτιάχνει η 30/10)
Τρία βήματα, δεκαπέντε λεπτά, μηδενικές δικαιολογίες:
- Security Advisor. Dashboard ή
supabase db advisors— κάθε ERROR είναι live διαρροή:0013table δημόσια προσβάσιμο ·0010view παρακάμπτει RLS ·0016materialized view εκτεθειμένο ·0023ευαίσθητα δημόσια ·0024policy ανοιχτή σε όλους. - Το 5-line query. Ποιες στήλες ταξινομούνται πρώτες είναι οι κακές (RLS off, ή RLS on με μηδέν policies):
select n.nspname as schema, c.relname as table_name, c.relrowsecurity as rls_enabled, (select count(*) from pg_policy p where p.polrelid = c.oid) as policies from pg_class c join pg_namespace n on n.oid = c.relnamespace where c.relkind = 'r' and n.nspname not in ('pg_catalog','information_schema') order by c.relrowsecurity, policies;
- Το curl της αλήθειας. Logged-out request με το anon key σε κάθε table με δεδομένα: rows = incident ·
[]ή error = σωστό. Μετά cross-account (B διαβάζει A;) και owner-check (ο νόμιμος δουλεύει;).
Checklist πριν το επόμενο deploy
- RLS + policies + grants στο ίδιο migration με το
create table— ποτέ «θα το βάλω μετά» - Μία policy ανά operation ·
USING+WITH CHECKστα UPDATE ·(select auth.uid())με παρένθεση - Views με
security_invoker = true· materialized views εκτόςpublicή χωρίς grants service_roleπουθενά σε client bundle · rotate αν βρέθηκεensure_rlsevent trigger για τα μελλοντικά tables (τα υπάρχοντα θέλουν χέρι)splinterlinter στο CI σαν gate:rls_disabled_in_public(ERROR),rls_enabled_no_policy,rls_policy_always_true
Για την 30ή Οκτωβρίου, καθαρά: σταματάει τις νέες διαρροές (νέα tables θέλουν ρητό grant). Δεν αγγίζει existing tables — κρατάνε grants και μένουν προσβάσιμα. Ό,τι διαρρέει σήμερα θα διαρρέει και 31/10. Το email που θα στείλει η Supabase αφορά το μέλλον· το παρελθόν θέλει το query από πάνω.
08 // Ελλάδα: τι ισχύει εδώ
Ξεκάθαρο: η UpGuard δεν κατονόμασε ελληνικά θύματα — τα δείγματα είναι ΗΠΑ, Καναδάς, Γαλλία, Φιλιππίνες, Ινδία. Αυτό δεν είναι άλλοθι: η μέθοδος αφήνει εκτός ό,τι δεν λέγεται users, και δεν χαρτογράφησε ανά χώρα. Αν τρέχεις Supabase που το έφτιαξε agent, υπέθεσε έκθεση μέχρι να τρέξεις το audit.
Δύο ελληνικά hooks: πρώτον, η έκθεση σημειώνει ότι η Ευρώπη τα πάει καλύτερα λόγω νομοθεσίας — το GDPR (72ωρο breach notification, data minimisation) είναι η μόνη «default» που δουλεύει όταν τα τεχνικά defaults αποτυγχάνουν. Δημόσια αναγνώσιμη βάση με Έλληνες users = περιστατικό με ρολόι. Δεύτερον, αν έφτιαξες το project σου με οδηγό σαν τον δικό μας vibe-coding οδηγό, ξαναπέρασέ το με το checklist της ενότητας 07 — το «δουλεύει» δεν ήταν ποτέ απόδειξη ότι «προστατεύει».
Ο κανόνας της μίας πρότασης
Στο Supabase, ο πίνακας είναι δημόσιος μέχρι η policy να πει ότι δεν είναι — και το AI δεν θα γράψει αυτή την policy αξιόπιστα. Νέα συνήθεια: κάθε table που δημιουργείται, RLS + policies πριν βγει η οθόνη που το χρησιμοποιεί. Τέσσερα SQL statements ανά πίνακα (ενότητα 04) — η διαφορά ανάμεσα σε launch και σε εμφάνιση στην επόμενη μελέτη.
09 // Συχνές ερωτήσεις
Το anon key του Supabase είναι μυστικό;
Όχι — είναι δημόσιο by design και ζει σε κάθε frontend bundle. Η ασφάλεια δεν είναι η απόκρυψη του κλειδιού αλλά οι RLS policies: το κλειδί φτάνει μόνο ό,τι επιτρέπει το RLS. Με RLS off, το «δημόσιο» κλειδί γίνεται de facto master key. Αντίθετα το service_role key παρακάμπτει κάθε RLS και δεν πρέπει να βγαίνει ποτέ στον browser — αν το βρεις σε client code, θεώρησέ το διαρροή και κάνε rotate αμέσως.
Γιατί τα AI tools φτιάχνουν βάσεις χωρίς RLS;
Γιατί το ασφαλές default ζει σε React component του dashboard (Table Editor), όχι στο API — το table-creation API δεν έχει καν παράμετρο RLS. Ο agent γράφει SQL (create table) και φεύγει· η προστασία είναι δεύτερη κλήση που κανείς δεν του ζήτησε. Το dashboard κάνει και τα δύο βήματα, ο agent μόνο το πρώτο.
Η αλλαγή της 30ής Οκτωβρίου τα φτιάχνει όλα;
Όχι. Από 30/10/2026 τα νέα tables χρειάζονται ρητό grant για να φανούν στο API — σταματάει τις νέες διαρροές. Αλλά τα existing tables κρατάνε τα grants τους και μένουν προσβάσιμα. Ό,τι διαρρέει σήμερα θα διαρρέει και 31/10 αν δεν βάλεις RLS χειροκίνητα.
Αρκεί να ανάψω το RLS;
Όχι — θέλει και policies, μία ανά operation (SELECT/INSERT/UPDATE/DELETE), αλλιώς κλειδώνεις και την ίδια σου την εφαρμογή. Και προσοχή στις θεατρικές policies: USING (true) για όλους, έλεγχος μόνο auth.role(), UPDATE χωρίς WITH CHECK, ξεχασμένα related tables και views χωρίς security_invoker — όλα περνάνε το «RLS on» και αποτυγχάνουν στον έλεγχο.
Υπάρχουν ελληνικά θύματα;
Η UpGuard δεν κατονόμασε ελληνικές περιπτώσεις — τα δείγματα που δημοσίευσε είναι ΗΠΑ, Καναδάς, Γαλλία, Φιλιππίνες, Ινδία. Αυτό δεν σημαίνει απουσία: η μέθοδος (probe μόνο για table users) αφήνει εκτός ό,τι λέγεται αλλιώς, και ο σαρωτής δεν χαρτογράφησε ανά χώρα. Αν τρέχεις Supabase φτιαγμένο από agent, υπέθεσε έκθεση μέχρι να το ελέγξεις.
Τι κάνω σε 15 λεπτά από τώρα;
Τρέξε Security Advisor (0013, 0010, 0016, 0023, 0024), το 5-line audit query για RLS/policies, και το curl ?select=* ως anon σε κάθε table με δεδομένα. Μετά: RLS + policies + grants στο ίδιο migration, security_invoker στα views, service_role εκτός browser, ensure_rls trigger για τα μελλοντικά, και splinter linter στο CI. Λεπτομέρειες στην ενότητα 07.