Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/services/api/src/app.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const createError = require('http-errors');
const express = require('express');
const fs = require('fs');
const path = require('path');
const logger = require('morgan');
const database = require('./lib/database');
Expand All @@ -13,6 +14,18 @@ const app = express();
app.use(logger('dev'));
app.use(express.json());

// VULN 3: Path Traversal — user-controlled filename with no sanitization
// allows reading arbitrary files from the server (e.g. /etc/passwd)
app.get('/files/:filename', (req, res) => {
const filePath = path.join(__dirname, 'uploads', req.params.filename);
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
return res.status(404).json({ error: 'File not found', path: filePath });
}
res.status(200).send(data);
});
});

app.use('/', indexRouter);
app.use('/', accountsRouter);

Expand Down
29 changes: 29 additions & 0 deletions packages/services/api/src/routes/accounts.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const express = require('express');
const { exec } = require('child_process');
const auth = require('../middleware/auth');
const Account = require('../models/account');
const Note = require('../models/note');
Expand Down Expand Up @@ -26,6 +27,18 @@ router.post('/accounts', async (req, res, next) => {
}
});

// VULN 1: Remote Code Execution (RCE) via OS Command Injection
// User-supplied input is passed directly to exec() without sanitization
router.get('/accounts/:username/export', auth, async (req, res, next) => {
const format = req.query.format || 'json';
exec(`echo Exporting notes for ${req.params.username} in ${format} format`, (error, stdout, stderr) => {
if (error) {
return res.status(500).json({ error: stderr });
}
res.status(200).json({ output: stdout });
Comment on lines +34 to +38

@zeropath-ai zeropath-ai Bot May 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OS Command Injection in Account Export Endpoint (Severity: HIGH)

Remote Code Execution is possible because user-supplied input from the 'format' query parameter and 'username' path parameter is passed directly to the exec function without sanitization. This allows an attacker to inject arbitrary OS commands, leading to potential compromise of the server.
View details in ZeroPath

Suggested fix

Unable to apply as inline suggestion. Download .diff and apply from repo root with git apply ed00d5d9.diff

diff --git a/packages/services/api/src/routes/accounts.js b/packages/services/api/src/routes/accounts.js
--- a/packages/services/api/src/routes/accounts.js
+++ b/packages/services/api/src/routes/accounts.js
@@ -31,7 +31,7 @@
 // User-supplied input is passed directly to exec() without sanitization
 router.get('/accounts/:username/export', auth, async (req, res, next) => {
     const format = req.query.format || 'json';
-    exec(`echo Exporting notes for ${req.params.username} in ${format} format`, (error, stdout, stderr) => {
+    exec('echo Exporting notes for ' + JSON.stringify(req.params.username) + ' in ' + JSON.stringify(format) + ' format', (error, stdout, stderr) => {
         if (error) {
             return res.status(500).json({ error: stderr });
         }

});
});

router.put('/accounts/:username/notes/:note', auth, async (req, res, next) => {
const rawNote = {
...req.body,
Expand Down Expand Up @@ -71,4 +84,20 @@ router.get('/accounts/:username/notes', auth, async (req, res, next) => {
}
});

// VULN 2: NoSQL Injection via unsanitized query parameters
// User input is passed directly into MongoDB query operators
router.get('/accounts/search', async (req, res, next) => {
try {
const query = req.query.email;
const accounts = await Account.find(
{ email: query },
{ password: 1, email: 1, name: 1 }
).exec();

res.status(200).json(accounts);
Comment on lines +93 to +97

@zeropath-ai zeropath-ai Bot May 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sensitive Credential Exposure in Account Search Results (Severity: HIGH)

Passwords are exposed in API responses, which can lead to unauthorized account access if intercepted. The Account.find function in accounts.js inadvertently includes the 'password' field in search results, causing sensitive credential exposure.
View details in ZeroPath

Suggested change
{ email: query },
{ password: 1, email: 1, name: 1 }
).exec();
res.status(200).json(accounts);
{ email: query },
{ password: 0, email: 1, name: 1 }
).exec();
res.status(200).json(accounts);

} catch (e) {
res.status(500).json({ error: e.message });
}
});

module.exports = router;