Affected Versions
| Affected | not applicable (first-party code) |
| Fixed in | not applicable (first-party code) — see fix commit |
| Ecosystem | npm (Express framework) |
| CVE / GHSA | not assigned |
| CWE | CWE-22 (Improper Limitation of a Pathname to a Restricted Directory) |
The Vulnerability Explained
The vulnerable code used Express's wildcard route handler app.get('*') to process all incoming GET requests through a single function. Inside this handler, a conditional check attempted to identify font file requests:
app.get("*", (req, res) => {
if (req.url.indexOf("font.woff2") > 0) {
res.sendFile("sketchybar-app-font.woff2", {root: "./dist"});
return;
}
res.send(getPreviewHTML());
});
The critical flaw: req.url.indexOf('font.woff2') > 0 is not a path validation mechanism. This substring check has two fatal weaknesses:
- Position sensitivity: The
> 0check excludes position 0, but an attacker can placefont.woff2anywhere else in the URL - No path normalization: The check operates on raw URL strings, not resolved filesystem paths
An attacker could craft requests like:
- GET /../../../etc/passwd?unused=font.woff2 — font.woff2 appears at position 28, satisfying the check while the actual path traversal targets arbitrary files
- GET /malicious/../../../font.woff2 — embedding the target substring within a traversal sequence
The {root: './dist'} option to res.sendFile() does prevent some traversal, but combined with the substring bypass, attackers could potentially reach unintended destinations depending on how Express resolves paths when the root option is combined with traversal sequences in the effective pathname.
The Fix
The remediation replaces the conditional logic with Express's native routing system:
app.get("/font.woff2", (req, res) => {
res.sendFile("sketchybar-app-font.woff2", {root: "./dist"});
});
app.get("*", (req, res) => {
res.send(getPreviewHTML());
});
What changed and why:
| Aspect | Before | After |
|---|---|---|
| Route matching | Single wildcard with manual substring check | Dedicated route + fallback wildcard |
| Validation | indexOf > 0 on raw URL |
Express's built-in path matching |
| Execution path | Always enters wildcard, conditionally branches | Only enters font handler for exact match |
Express's app.get('/font.woff2') performs strict path matching before your code executes. The framework normalizes incoming paths and rejects traversal sequences at the routing layer, never reaching your handler with malicious input. The wildcard handler now only receives requests that genuinely don't match defined routes.
Key Takeaways
-
Substring checks on URLs are not security boundaries:
indexOf,includes, and similar string methods can be bypassed by embedding target strings in unexpected positions—query parameters, path segments, or encoded forms. -
Express routes are your validation layer: Define explicit routes for static assets rather than inspecting
req.urlinside catch-all handlers. The framework's path parsing and normalization runs before your code. -
The
rootoption insendFileis not absolute protection: While it restricts the base directory, interaction with unsanitized path components can still produce unexpected resolutions depending on framework behavior. -
Order matters in Express route registration: The fix places the specific font route before the wildcard handler. Express matches routes in registration order—specific handlers must precede generic ones.
-
Return statements in security conditionals indicate fragile design: Needing
returnto prevent fall-through suggests the control flow mixes concerns. Separate routes eliminate this class of error.
How Orbis AppSec Detected This
Source: The req.url property on incoming HTTP requests
Sink: res.sendFile() invoked with {root: './dist'} and a filename derived from conditional logic on unsanitized URL input
Missing control: No path normalization, no strict equality validation, and reliance on substring presence rather than exact route matching
CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Fix: Replaced the conditional substring check within a wildcard route with a dedicated app.get('/font.woff2') route that leverages Express's built-in path validation before any file operations occur.
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
This vulnerability demonstrates how seemingly defensive code—checking for a filename before serving it—can introduce exactly the weakness it attempts to prevent. The indexOf check created a false sense of security while providing attackers a flexible injection point. The fix leverages the framework's strengths: Express designed route matching to solve this problem, and using it eliminates an entire category of path manipulation attacks. When handling static files, let your router validate paths rather than implementing validation in application code.