Skip to content

Commit 44ef36d

Browse files
perf: make express.json route-specific to prevent DoS on unhandled routes
Moved the `express.json()` middleware from the global scope to be a route-specific middleware on the `/v1/chat/completions` endpoint. This optimization prevents the Express application from unnecessarily buffering and parsing large JSON bodies sent to arbitrary or non-existent routes (like the 404 handler). By moving the JSON parsing logic into the route and shifting the corresponding syntax error handler below the route definition, the application becomes significantly more resilient against CPU exhaustion denial of service attacks without introducing regressions. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: shenald-dev <245350826+shenald-dev@users.noreply.github.com>
1 parent aca68ee commit 44ef36d

6 files changed

Lines changed: 35 additions & 24 deletions

File tree

.jules/bolt.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,6 @@ In highly trafficked functions such as `isValidModel`, `isValidMessagesArray`, a
172172

173173
Action:
174174
Optimized validation helper logic in `src/index.js` to strictly rely on explicit type checks and avoid double negations (`!!`). Simplified truthiness evaluations into direct equality checks (`trim() !== ''` instead of `!!trim()`).
175+
2026-04-25 — DoS Mitigation via Route-Specific Parsing
176+
Learning: Global `express.json()` middleware forces the application to buffer and parse request bodies even for unknown or non-existent routes, exposing a Denial of Service (DoS) vulnerability via large payloads to 404 endpoints.
177+
Action: Apply `express.json()` strictly as route-specific middleware to the exact endpoints that require body parsing, and ensure dependent JSON error handlers are positioned correctly after those specific route definitions in the middleware chain.

.jules/warden.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,9 @@ Observation / Pruned:
126126
Assessed repository state following previous optimizations. Since no new functional or architectural changes were introduced by the prior agent run, no new release cut or version bump is warranted. Maintained semantic integrity by preserving the existing v1.1.23 state.
127127
Alignment / Deferred:
128128
Release deferred. Repository state verified and stable.
129+
130+
2026-04-25 — Assessment & Lifecycle
131+
Observation / Pruned:
132+
Assessed JULES/BOLT's optimization changing global `express.json` middleware into a route-specific middleware. This prevents unhandled routes (e.g. 404s) from attempting to buffer and parse large JSON payloads, saving CPU cycles and mitigating DoS vectors. The JSON error handler was effectively moved correctly to preserve functionality. Ran full tests and robustness scripts to verify correct validation edge cases pass. Zero unused files or exports were identified for pruning.
133+
Alignment / Deferred:
134+
Appended release notes for performance and security patch. Version bumped to 1.1.24.

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
# Changelog
1+
## v1.1.24 - 2026-04-25
2+
### Changed
3+
- **Security/Performance:** Modified the `express.json()` middleware to act as a route-specific middleware on `/v1/chat/completions` rather than globally. This prevents unnecessary JSON parsing for non-existent endpoints (like 404 routes), mitigating potential CPU exhaustion DoS vectors from large arbitrary payloads.
24

3-
All notable changes to this project will be documented in this file.
45

56
## [1.1.23] - 2026-04-24
67
### Performance

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "one-api",
3-
"version": "1.1.23",
3+
"version": "1.1.24",
44
"description": "One API to rule them all. Unified gateway for 20+ LLM providers. OpenAI-compatible, single binary, zero config.",
55
"main": "src/index.js",
66
"scripts": {

src/index.js

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,28 +48,10 @@ app.get('/health', (req, res) => {
4848

4949
// Compress all responses to reduce bandwidth and latency
5050
app.use(compression());
51-
// Set a larger JSON limit since LLM contexts can be quite large
52-
app.use(express.json({ limit: '10mb' }));
5351

5452
const ERROR_INVALID_JSON = Buffer.from(JSON.stringify({ error: 'Invalid JSON payload' }));
5553
const ERROR_PAYLOAD_TOO_LARGE = Buffer.from(JSON.stringify({ error: 'Payload too large' }));
5654

57-
// Handle invalid JSON gracefully
58-
app.use((err, req, res, next) => {
59-
if (res.headersSent) {
60-
return next(err);
61-
}
62-
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
63-
res.setHeader('Content-Type', 'application/json; charset=utf-8');
64-
return res.status(400).send(ERROR_INVALID_JSON);
65-
}
66-
if (err.type === 'entity.too.large') {
67-
res.setHeader('Content-Type', 'application/json; charset=utf-8');
68-
return res.status(413).send(ERROR_PAYLOAD_TOO_LARGE);
69-
}
70-
next(err);
71-
});
72-
7355
// API endpoints
7456
function isValidModel(model) {
7557
return typeof model === 'string' && model.trim() !== '';
@@ -108,7 +90,10 @@ const ERROR_MISSING_MESSAGES = Buffer.from(JSON.stringify({ error: 'Missing or i
10890
const ERROR_TOO_MANY_MESSAGES = Buffer.from(JSON.stringify({ error: 'Too many messages' }));
10991
const ERROR_MALFORMED_MESSAGE = Buffer.from(JSON.stringify({ error: 'Malformed message object' }));
11092

111-
app.post('/v1/chat/completions', (req, res) => {
93+
// Set a larger JSON limit since LLM contexts can be quite large
94+
const jsonParser = express.json({ limit: '10mb' });
95+
96+
app.post('/v1/chat/completions', jsonParser, (req, res) => {
11297
const { model, messages } = req.body || {};
11398
if (!isValidModel(model)) {
11499
return res.status(400).send(ERROR_MISSING_MODEL);
@@ -131,6 +116,22 @@ app.post('/v1/chat/completions', (req, res) => {
131116
res.status(200).send(payload);
132117
});
133118

119+
// Handle invalid JSON gracefully
120+
app.use((err, req, res, next) => {
121+
if (res.headersSent) {
122+
return next(err);
123+
}
124+
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
125+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
126+
return res.status(400).send(ERROR_INVALID_JSON);
127+
}
128+
if (err.type === 'entity.too.large') {
129+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
130+
return res.status(413).send(ERROR_PAYLOAD_TOO_LARGE);
131+
}
132+
next(err);
133+
});
134+
134135
const ERROR_NOT_FOUND = Buffer.from(JSON.stringify({ error: 'Not found' }));
135136

136137
// 404 handler — return JSON for unknown routes

0 commit comments

Comments
 (0)