A real review of this site's code, mapped to the OWASP Top 10. Every finding links to the exact commit that fixed it - verifiable in the repo's public history, not a promise.
Live filter· real 30-day mix
010203site04 · log
01Platformoutside this site
02Classifier824 detected
03Decoys156 trapped
04Hourly cron0 blocks
Mix between categories: real, last 30 days. The amount of clean traffic is illustrative: the site does not count clean visits.
/01Security Operations · live
What arrived this month, and what happened to it.
This site runs its own security observability engine (a "micro-SIEM"): an OWASP-aligned threat classifier runs in the middleware on every request, decoy endpoints confirm malicious intent, and an hourly cron applies temporary blocks and statistically detects anomalies (z-score over a 30-day baseline). The numbers below are real aggregates from the last 30 days.
Computed 12 minutes ago · refreshed every 3 h
Detected (30d)
824
Automatic blocks
0
Categories detected
6
Breakdown by category
Blocked repeat offender429
CMS/panel reconnaissance194
Decoy endpoint touched156
Secrets/config probing41
csp_violation3
Path traversal / LFI1
Geographic origin
US163
DE87
HK75
SE74
BO65
SG63
Daily trend (14 days)
2026-10-08:20 events
2026-10-09:22 events
2026-10-10:17 events
2026-10-11:17 events
2026-10-12:21 events
2026-10-13:25 events
2026-10-14:19 events
2026-10-15:19 events
2026-10-16:16 events
2026-10-17:17 events
2026-10-18:16 events
2026-10-19:20 events
2026-10-20:18 events
2026-10-21:1 events
2026-10-082026-10-21
How it works (4 layers)
01Automatic DDoS mitigation and the platform firewall (Vercel).
02A custom classifier in the middleware: OWASP signatures, two-layer durable rate limiting, IP blocking.
03Logging of every hostile event (with burst deduplication) and decoy endpoints that confirm malicious intent.
The panel's repositories page used two endpoints that lived under /api/github/, outside the path the middleware protects. Without signing in, one listed the private repositories the server's token can reach and the other created projects or changed their visibility.
Fix
Both moved under /api/admin/github/, where the middleware requires the admin session. Since then, every route the panel uses lives under /api/admin.
The middleware guards compare literal paths. With a trailing slash, /docs/presentacion/ did not match the private route and the private deck was served without signing in.
Fix
The middleware computes the canonical path once (no trailing slash, no language prefix) and every guard compares against it. A test pins that the path arrives the same with a slash, without it, and under /en.
A07:2021 · Identification and Authentication Failuressrc/lib/webauthn.ts
Problem
The WebAuthn challenge traveled in an unsigned cookie. Anyone holding a captured passkey response could replay it by writing the old challenge into the cookie: the server did not remember which challenge it had issued.
Fix
The challenge is stored server-side, in its own table with an expiry, and consumed exactly once. An invented or replayed challenge no longer exists in the database and verification fails.
Registering a passkey did not require signing in again
mediumfixed
A07:2021 · Identification and Authentication Failuressrc/pages/api/admin/webauthn/registration/verify.ts
Problem
A stolen session cookie was enough to register the attacker's own passkey on the account, and that passkey kept opening the panel even after the session was revoked.
Fix
Adding a passkey requires a sign-in from less than 10 minutes ago. Every addition or removal sends an immediate alert and is recorded in the micro-SIEM.
A07:2021 · Identification and Authentication Failuresauth.config.ts
Problem
Panel access was decided by the GitHub username, which can be changed and leaves the old name free. Whoever registered that freed name would pass the allowlist.
Fix
The login also requires the account's numeric id, which GitHub never reassigns.
The endpoint behind /log and the home page uses a token that also sees private repositories, and it published their names and commit messages, including client projects.
Fix
Private work counts in the totals but never appears by name or message: only a repository explicitly marked public is published. A URL with parameters redirects to the clean one, so nobody can bypass the cache.
The events table also keeps an audit trail of legitimate actions (panel sign-ins, clients checking their payments). This very page counted them as threats, its daily chart revealed which days the panel was used, and bulk blocking could block clients and students.
Fix
Audit categories were split from threat categories in a single place in the code and are excluded from this page, from anomaly detection, and from bulk blocking. The trail is reviewed separately, only in the panel.
A revoked session got back in when the database was down
mediumfixed
A07:2021 · Identification and Authentication Failuressrc/middleware.ts
Problem
The middleware checked whether a panel session had been revoked, but let it through if the database did not answer. An outage, or a quota exhausted on purpose, made any stolen and already revoked cookie valid again.
Fix
That check fails closed: if it cannot tell whether the session was revoked, the panel answers 503. It is the deliberate exception to the fail-open rule of the rest of the site's security.
Twenty-five places embedded JSON in <script> tags, including the public structured data with project titles read from the database. A title containing "</script>" closed the tag and whatever followed ran as code.
Fix
All of them go through a single function that escapes <, > and & before embedding, and links coming from the database are validated to accept only http and https.
The certificate upload endpoint derived the final file extension from the client-supplied filename, and only validated the MIME type declared by the client itself (easily forged). A file uploaded as "foo.html" with a fake Content-Type ended up served as HTML from /assets/certs. SVG was also on the allowed-types list, and an SVG can contain an executable <script>.
Fix
The extension is now derived from a fixed MIME → extension map, never from the filename. SVG was removed from the allowed types.
The public contact endpoint accepted unlimited inserts into the database with no rate limit, no email format validation, no field length limits, and malformed JSON produced an unhandled 500 error.
Fix
The project's existing rate limiter was applied (5 submissions/min per IP), along with email format validation, per-field length limits, and explicit handling of invalid JSON.
Sessions without a login claim bypassed the allowlist
mediumfixed
A01:2021 · Broken Access Controlsrc/middleware.ts
Problem
The middleware only revalidated the GitHub allowlist when the session carried the "login" claim. A session without that claim passed the check by default instead of being rejected.
Fix
The admin gate now requires the claim on every session: without a valid login on the allowlist, the response is 403, no exceptions.
The middleware already set several security headers (nosniff, Referrer-Policy, X-Frame-Options on admin) but included no Content-Security-Policy or Strict-Transport-Security on any route.
Fix
A restrictive CSP (default-src self, frame-ancestors none) and HSTS with preload were added to every response, public and admin alike.
The Wompi event receiver verifies the HMAC signature before processing any event, rejects events older than 6 hours (anti-replay), and validates that the amount/currency match the expected payment before applying a state transition.
The credentials vault encrypts every value with AES-256-GCM (authenticated encryption) before writing it to the database. The key lives only in the server environment, never in the database or in backups.
Authorization is revalidated against the allowlist both in the login callback and in the middleware, on every request - logging in once is not enough.
sign-inGitHub OAuth✓
request /adminallowlist re-checked✓
request /adminallowlist re-checked✓
request /adminallowlist re-checked✓
session without login403✕ rejected
Illustrative example
Parameterized queries
All data access goes through Drizzle ORM with parameterized queries; there is no SQL string interpolation anywhere in the project.
what the attacker types
' OR 1=1 --
query
SELECT * FROM clients WHERE email = ?
parameter 1 →"' OR 1=1 --"
0 rows: the input travels as data, not code
Illustrative example
/04Methodology
How it was done.
01
Review
Two reviews of this repository's real code, not a theoretical exercise. July's covered access control, file uploads, public endpoints, secrets, payment webhooks, and response headers; September's covered the panel: passkeys, sessions and their revocation, the audit trail, and what the site publishes from the database.
02
Fix
Every finding was classified against the OWASP Top 10 (2021) and links to the commit that closed it. Some commits close two findings, and one carries other changes alongside the fix: the link points to the real commit, not one staged for this page. Every fix went through the test suite and a full build.
03
Publish
This page is updated with every audit. Only fixed findings are published: whatever a review leaves open does not appear here until it is closed. What you see is the real state of the code in production.
Hi. I'm Mike's AI assistant: I can help with questions about plans, timelines and payments, and I can work out a price estimate. Whatever I tell you, Mike confirms on WhatsApp.