A Firewall for My SvelteKit SSR
I launched my SvelteKit app, expecting a quiet corner of the internet. Instead, hundreds of automated outlaws were flooding my logs. Each attempt triggered a full-scale, resource-intensive response from my server, searching the database for invalid ids with wild aggregations, making elastic search requests, you can picture it I guess.
But with all the routes in my app, I couldn't simply do a case per case attack analysis. In addition, that would have polluted my business logic code with pretty insane heuristics. I couldn't be solved by a call to the same method, because routes and their valid parameters differ on each endpoint.
It couldn't be therefore solved in the hooks neither, in addition because we want the expedition job to be effiective, we cannot start exploring the routes we have in the app and the allowed parameters by listing the /routes subfolders on each request.
In this article, I expose the solution I built to:
- keep my app code untouched
- adapt to new routes
- allow new validation mechanisms to be added
The Trampoline for Bots
Once my server was online, the logs told a clear story: a constant stream of probes for wp-admin.php
, phpMyAdmin
, and other common vulnerabilities. These weren't mistakes from real users; they were automated scripts on a fishing nasty attempts.
The core issue was architectural. For every request—legitimate or not—my SvelteKit server would dutifully prepare the cavalry: running database queries, searching Elasticsearch, and executing complex logic. It was expensive work being performed for requests that deserved a slap of a door.
Never trust the user? How?
The principle of "never trust the user input" is web development 101. However, in SvelteKit, applying this principle efficiently is tricky. Within the hooks
file, you lack the context of your application's specific routes and parameters. There's no built-in mechanism to say, "This URL pattern is invalid; stop here."
Embedding security checks directly into my page and API logic was a non-starter. It would clutter clean business code with repetitive validation, making it harder to read and maintain. Security should be an aspect oriented solution, the services should feel safe and secure like home (in my programming style).
The Challenge of a Custom Solution
Creating this gatekeeper is complex because every application is unique.
- URLs vary: Parameters can be IDs (
/user/123
), slugs (/blog/my-post
), or even optional (/archive/2023/[[month]]
). - Validation differs: A user ID must be checked against a database, while a slug might only need a character set validation.
- The margin for error is zero: The system must be stringent enough to block attacks but careful to never lock out a real user.
The Architecture: A Compile Time Code Generator
My solution was to move the validation upstream. At build time: a script scans the /routes
directory and compiles a manifest of every valid URL pattern and its parameter rules. This manifest becomes the bouncer's list.
Level 1: The Guest List (URL Pattern Matching)
The system translates my /routes
file structure into a set of regular expressions.
/api/users/[id]
becomes^/api/users/([^/]+)$
/blog/[seoName]/comments/[[id]]
becomes^/blog/([^/]+)/comments/([^/]+)?$
Any HTTP request that fails to match a pattern is immediately turned away with a clean 404, before any application logic is invoked. Since the patterns were prepared at compile time, this is a non intensive operation
Level 2: Parameter Validation By Naming Convention
By analyzing parameter names, the system applies validation rules: a param name containing one of the listed name suffixes is immediately mapped to the app predefines validators
- Parameters ending by id like
id
,userId
, orpostId
are validated as database IDs (mongo ObjectId in my case). - A parameter like
seoName
is checked for suspicious characters, and allows ObjectId (as I allow drafts to be accessed by their real id). - Parameters like
entityType
are validated against an enum
If the script encounters a parameter name without a predefined validation rule, it logs a warning during the build process, forcing me to make a conscious decision.
Level 3: Spotting the Attacks
A final layer checks for obvious abuse patterns, such as parameters longer than 200 characters or those containing blatant injection attempts.
Ensuring Integrity
Remember we don't the user to get rejected by error? A static list is dangerous in this regard if it becomes outdated. Therefore I integrated two safeguards.
1. The CI/CD Checklist
A GitHub Action runs before every deployment, executing a command to verify the route manifest is current.
pnpm check-route-patterns
If I add a new route but forget to define its validation rules, the build process detects it and fails. This prevents new, unprotected endpoints from getting rejected.
2. Testing the Guest List
A suite of tests throws a variety of valid and invalid URLs at the pattern matcher. This ensures that legitimate requests with optional parameters or complex slugs are always let in, and that the regex patterns remain accurate.
The Result
Expensive operations are now reserved for legitimate traffic. Invalid requests are stopped at the gate with minimal resource expenditure. The server will cost me less, the logs are cleaner, and my business methods are still feeling like home.
This is my first article on svelter protected by the very solution I am talking about in this article (still not deployed at the time of writing). If you found the idea worth reading, if you think it could fire back, or if you just want to make my day, just drop a comment, upvote, let me know you appreciate it. Also if you want me to share the code of this firewall, let me know, I'll make some time to extract it from the project.
Update:
I received a message from Zach, the CEO of Sherpa two hours after posting on reddit about this article:
Zach @ sherpa.sh — 22:30
Hi Zied. I saw your post on reddit about the php admin page. I just enabled the WAF on your application. It'll take a few days for the smart algorithm to start detecting the bots, but it should help with the request issues you are having.
Frankly, I am not surprized when startups like Sherpa succeed this fast, I mean how can someone not become an ambassador of the brand after this quality of interactions.