Skip to content

Commit e55c0b2

Browse files
fix(rsc-demo): address CodeQL security alerts
- Add input validation for note IDs to prevent path traversal - Cap /sleep/:ms endpoint to max 10 seconds to prevent DoS - Use proper regex escaping in test file 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent b816818 commit e55c0b2

File tree

3 files changed

+52
-11
lines changed

3 files changed

+52
-11
lines changed

apps/rsc-demo/packages/app1/server/api.server.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,11 @@ app.put(
609609
handleErrors(async function (req, res) {
610610
const now = new Date();
611611
const updatedId = Number(req.params.id);
612+
// Validate ID is a positive integer to prevent path traversal
613+
if (!Number.isInteger(updatedId) || updatedId <= 0) {
614+
res.status(400).send('Invalid note ID');
615+
return;
616+
}
612617
const pool = await getPool();
613618
await pool.query(
614619
'update notes set title = $1, body = $2, updated_at = $3 where id = $4',
@@ -626,9 +631,15 @@ app.put(
626631
app.delete(
627632
'/notes/:id',
628633
handleErrors(async function (req, res) {
634+
const noteId = Number(req.params.id);
635+
// Validate ID is a positive integer to prevent path traversal
636+
if (!Number.isInteger(noteId) || noteId <= 0) {
637+
res.status(400).send('Invalid note ID');
638+
return;
639+
}
629640
const pool = await getPool();
630-
await pool.query('delete from notes where id = $1', [req.params.id]);
631-
await unlink(path.resolve(NOTES_PATH, `${req.params.id}.md`));
641+
await pool.query('delete from notes where id = $1', [noteId]);
642+
await unlink(path.resolve(NOTES_PATH, `${noteId}.md`));
632643
sendResponse(req, res, null);
633644
})
634645
);
@@ -645,18 +656,26 @@ app.get(
645656
app.get(
646657
'/notes/:id',
647658
handleErrors(async function (req, res) {
659+
const noteId = Number(req.params.id);
660+
// Validate ID is a positive integer
661+
if (!Number.isInteger(noteId) || noteId <= 0) {
662+
res.status(400).send('Invalid note ID');
663+
return;
664+
}
648665
const pool = await getPool();
649666
const {rows} = await pool.query('select * from notes where id = $1', [
650-
req.params.id,
667+
noteId,
651668
]);
652669
res.json(rows[0]);
653670
})
654671
);
655672

656673
app.get('/sleep/:ms', function (req, res) {
674+
// Cap the sleep time to prevent DoS (max 10 seconds)
675+
const ms = Math.min(Math.max(0, parseInt(req.params.ms, 10) || 0), 10000);
657676
setTimeout(() => {
658677
res.json({ok: true});
659-
}, req.params.ms);
678+
}, ms);
660679
});
661680

662681
app.use(express.static('build'));

apps/rsc-demo/packages/app2/server/api.server.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,11 @@ app.put(
462462
handleErrors(async function (req, res) {
463463
const now = new Date();
464464
const updatedId = Number(req.params.id);
465+
// Validate ID is a positive integer to prevent path traversal
466+
if (!Number.isInteger(updatedId) || updatedId <= 0) {
467+
res.status(400).send('Invalid note ID');
468+
return;
469+
}
465470
const pool = await getPool();
466471
await pool.query(
467472
'update notes set title = $1, body = $2, updated_at = $3 where id = $4',
@@ -479,9 +484,15 @@ app.put(
479484
app.delete(
480485
'/notes/:id',
481486
handleErrors(async function (req, res) {
487+
const noteId = Number(req.params.id);
488+
// Validate ID is a positive integer to prevent path traversal
489+
if (!Number.isInteger(noteId) || noteId <= 0) {
490+
res.status(400).send('Invalid note ID');
491+
return;
492+
}
482493
const pool = await getPool();
483-
await pool.query('delete from notes where id = $1', [req.params.id]);
484-
await unlink(path.resolve(NOTES_PATH, `${req.params.id}.md`));
494+
await pool.query('delete from notes where id = $1', [noteId]);
495+
await unlink(path.resolve(NOTES_PATH, `${noteId}.md`));
485496
sendResponse(req, res, null);
486497
})
487498
);
@@ -498,18 +509,26 @@ app.get(
498509
app.get(
499510
'/notes/:id',
500511
handleErrors(async function (req, res) {
512+
const noteId = Number(req.params.id);
513+
// Validate ID is a positive integer
514+
if (!Number.isInteger(noteId) || noteId <= 0) {
515+
res.status(400).send('Invalid note ID');
516+
return;
517+
}
501518
const pool = await getPool();
502519
const {rows} = await pool.query('select * from notes where id = $1', [
503-
req.params.id,
520+
noteId,
504521
]);
505522
res.json(rows[0]);
506523
})
507524
);
508525

509526
app.get('/sleep/:ms', function (req, res) {
527+
// Cap the sleep time to prevent DoS (max 10 seconds)
528+
const ms = Math.min(Math.max(0, parseInt(req.params.ms, 10) || 0), 10000);
510529
setTimeout(() => {
511530
res.json({ok: true});
512-
}, req.params.ms);
531+
}, ms);
513532
});
514533

515534
app.use(express.static('build'));

apps/rsc-demo/packages/e2e/rsc/server.directive-transform.test.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ function createLoaderContext(resourcePath) {
1515
};
1616
}
1717

18+
// Escape special regex characters in a string (including backslashes)
19+
function escapeRegExp(string) {
20+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
21+
}
22+
1823
// === 'use client' TRANSFORMATION TESTS ===
1924

2025
test("'use client' transformation: replaces module with createClientModuleProxy call", (t) => {
@@ -65,9 +70,7 @@ export function Widget() { return <div>Widget</div>; }
6570
const expectedUrl = `file://${testPath}`;
6671
assert.match(
6772
result,
68-
new RegExp(
69-
`createClientModuleProxy\\('${expectedUrl.replace(/\//g, '\\/')}`
70-
)
73+
new RegExp(`createClientModuleProxy\\('${escapeRegExp(expectedUrl)}`)
7174
);
7275
}
7376
});

0 commit comments

Comments
 (0)