Mike (@mikerb95)CodeByMike
Back
/securityOWASP audit

Security, with evidence.

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
  1. 01Platformoutside this site
  2. 02Classifier824 detected
  3. 03Decoys156 trapped
  4. 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)

  1. 01Automatic DDoS mitigation and the platform firewall (Vercel).
  2. 02A custom classifier in the middleware: OWASP signatures, two-layer durable rate limiting, IP blocking.
  3. 03Logging of every hostile event (with burst deduplication) and decoy endpoints that confirm malicious intent.
  4. 04Hourly cron: aggregates, detects statistical anomalies, auto-blocks repeat offenders, and alerts.

Pipeline SLO targets

  • Overhead added per request (p99)≤ 5 ms
  • Detection → automatic block≤ 60 min
  • Detection → alert (critical)≤ 60 s
  • False-positive blocks< 0.1%
  • Raw event retention90 days

By design, this page never shows full IPs, exact detection rule names, or which routes are decoy endpoints - that would hand an attacker the playbook.

/02Fixed findings · 13

Every finding, closed by a commit.

As you scroll, each commit closes its finding.

  1. Panel audit · September 2026 · 9

  2. Two panel endpoints answered without a session

    highfixed
    A01:2021 · Broken Access Controlsrc/pages/api/admin/github/toggle.ts
    Problem

    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.

    2026-09-22 · commit 5ccb7ec ↗
  3. A trailing slash bypassed the login

    mediumfixed
    A01:2021 · Broken Access Controlsrc/middleware.ts
    Problem

    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.

    2026-09-22 · commit f978724 ↗
  4. The browser controlled the passkey challenge

    mediumfixed
    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.

    2026-09-23 · commit f723b6b ↗
  5. 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.

    2026-09-23 · commit ddc84af ↗
  6. The allowlist trusted the GitHub username

    mediumfixed
    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.

    2026-09-23 · commit ddc84af ↗
  7. Public activity showed private repositories

    highfixed
    A01:2021 · Broken Access Controlsrc/pages/api/github/activity.ts
    Problem

    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.

    2026-09-23 · commit 1a4ecf7 ↗
  8. The security showcase published panel use as attacks

    mediumfixed
    A04:2021 · Insecure Designsrc/lib/security/audit.ts
    Problem

    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.

    2026-09-24 · commit 5bfcfdf ↗
  9. 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.

    2026-09-24 · commit 5bfcfdf ↗
  10. JSON embedded in <script> without escaping

    mediumfixed
    A03:2021 · Injectionsrc/lib/json-script.ts
    Problem

    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.

    2026-09-24 · commit f2a9496 ↗
  11. First audit · July 2026 · 4

  12. Stored XSS via unvalidated file extension

    highfixed
    A03:2021 · Injectionsrc/pages/api/admin/upload.ts
    Problem

    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.

    2026-07-06 · commit d439054 ↗
  13. Contact form with no rate limit or validation

    mediumfixed
    A04:2021 · Insecure Designsrc/pages/api/contact.ts
    Problem

    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.

    2026-07-06 · commit 779f4b0 ↗
  14. 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.

    2026-07-06 · commit 14b08e2 ↗
  15. CSP and HSTS were missing from responses

    mediumfixed
    A05:2021 · Security Misconfigurationsrc/middleware.ts
    Problem

    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.

    2026-07-06 · commit 32bcf0d ↗
/03Controls already in place

What was already right, demonstrated.

Verified payment webhooks

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.

Secrets encrypted at rest

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.

Defense in depth on /admin

Authorization is revalidated against the allowlist both in the login callback and in the middleware, on every request - logging in once is not enough.

Parameterized queries

All data access goes through Drizzle ORM with parameterized queries; there is no SQL string interpolation anywhere in the project.

/04Methodology

How it was done.

  1. 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.

  2. 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.

  3. 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.