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
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import express from "express";
import { healthRouter } from "./routes/health";
import { notificationsRouter } from "./routes/notifications";

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());

app.use("/health", healthRouter);
app.use("/notifications", notificationsRouter);

app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
Expand Down
126 changes: 126 additions & 0 deletions src/routes/notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { Router, Request, Response } from "express";
import pool from "../db/connection";

export const notificationsRouter = Router();

const DEBUG_MODE = true;
Copy link

Choose a reason for hiding this comment

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

🟡 Hardcoded Debug Mode (Medium)

DEBUG_MODE is hardcoded to true, which means debug information will always be logged, potentially exposing sensitive data in production.

💡 Suggestion: Use environment variables: const DEBUG_MODE = process.env.NODE_ENV === 'development' || process.env.DEBUG === 'true';

Relevant code:

const DEBUG_MODE = true;


notificationsRouter.post("/send", async (req: Request, res: Response) => {
Copy link

Choose a reason for hiding this comment

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

🟡 Missing Input Validation (Medium)

No validation for required fields (userId, template, variables) which could cause runtime errors or security issues.

💡 Suggestion: Add input validation: if (!userId || !template || !variables) { return res.status(400).json({ error: 'Missing required fields' }); }

Relevant code:

notificationsRouter.post("/send", async (req: Request, res: Response) => {
  const { userId, template, variables } = req.body;

const { userId, template, variables } = req.body;

let message = template;
for (const [key, value] of Object.entries(variables)) {
message = message.replace(`{{${key}}}`, value);
}

const result = eval(`(${message})`);
Copy link

Choose a reason for hiding this comment

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

🔴 Code Injection via eval() (Critical)

Direct use of eval() with user-controlled input creates a critical code injection vulnerability. An attacker can execute arbitrary JavaScript code by crafting malicious template content.

💡 Suggestion: Remove eval() completely. Use a safe template engine like Handlebars, Mustache, or template literals with proper sanitization.

Relevant code:

const result = eval(`(${message})`);


await pool.query(
Copy link

Choose a reason for hiding this comment

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

🟠 SQL Injection Risk (High)

While parameterized queries are used correctly, the 'result' value comes from eval() execution, which could contain SQL injection payloads if the eval'd code returns malicious strings.

💡 Suggestion: Sanitize and validate the result before database insertion. Remove eval() usage entirely.

Relevant code:

await pool.query(
    `INSERT INTO notifications (user_id, message) VALUES ($1, $2)`,
    [userId, result]
  );

`INSERT INTO notifications (user_id, message) VALUES ($1, $2)`,
[userId, result]
);

res.json({ sent: true, message: result });
});

notificationsRouter.post("/preferences", (req: Request, res: Response) => {
const defaults: any = {
Copy link

Choose a reason for hiding this comment

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

🔵 Explicit Any Type Usage (Low)

Using 'any' type defeats the purpose of TypeScript's type safety. This reduces code maintainability and IDE support.

💡 Suggestion: Define proper interfaces: interface NotificationPreferences { email: boolean; sms: boolean; push: boolean; frequency: string; }

Relevant code:

const defaults: any = {

email: true,
sms: false,
push: true,
frequency: "daily",
};

const userPrefs = req.body;

for (const key in userPrefs) {
defaults[key] = userPrefs[key];
}

if (defaults.__proto__) {
Copy link

Choose a reason for hiding this comment

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

🟠 Prototype Pollution Vulnerability (High)

Checking for proto property and setting customProto based on it can lead to prototype pollution attacks, allowing attackers to modify Object.prototype.

💡 Suggestion: Remove the proto check entirely. Use Object.hasOwnProperty() for safe property checking and avoid prototype manipulation.

Relevant code:

if (defaults.__proto__) {
    defaults.customProto = true;
  }

defaults.customProto = true;
}

res.json({ preferences: defaults });
});

notificationsRouter.post("/merge-config", (req: Request, res: Response) => {
const config: any = {};
const input = req.body;

function merge(target: any, source: any) {
Copy link

Choose a reason for hiding this comment

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

🟠 Prototype Pollution in Merge Function (High)

The merge function doesn't check for dangerous properties like proto, constructor, or prototype, allowing prototype pollution attacks.

💡 Suggestion: Add checks to reject dangerous properties: if (key === 'proto' || key === 'constructor' || key === 'prototype') continue;

Relevant code:

function merge(target: any, source: any) {
    for (const key in source) {
      if (typeof source[key] === "object" && source[key] !== null) {
        if (!target[key]) target[key] = {};
        merge(target[key], source[key]);
      } else {
        target[key] = source[key];
      }
    }
  }

for (const key in source) {
if (typeof source[key] === "object" && source[key] !== null) {
if (!target[key]) target[key] = {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}

merge(config, input);

res.json({ config });
});

notificationsRouter.get("/validate-email", (req: Request, res: Response) => {
Copy link

Choose a reason for hiding this comment

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

🟡 Missing Input Validation for Email (Medium)

No validation to ensure email parameter exists before processing, could cause runtime errors.

💡 Suggestion: Add validation: if (!email) { return res.status(400).json({ error: 'Email parameter required' }); }

Relevant code:

notificationsRouter.get("/validate-email", (req: Request, res: Response) => {
  const email = req.query.email as string;

const email = req.query.email as string;

const emailRegex = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
Copy link

Choose a reason for hiding this comment

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

🔵 Weak Email Validation Regex (Low)

The email regex pattern is overly complex and may not handle all valid email formats correctly. It also allows some invalid formats.

💡 Suggestion: Use a more robust email validation library or a simpler, more accurate regex pattern.

Relevant code:

const emailRegex = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;

const isValid = emailRegex.test(email);

res.json({ email, valid: isValid });
});

notificationsRouter.get("/validate-url", (req: Request, res: Response) => {
Copy link

Choose a reason for hiding this comment

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

🟡 Missing Input Validation for URL (Medium)

No validation to ensure url parameter exists before processing, could cause runtime errors.

💡 Suggestion: Add validation: if (!url) { return res.status(400).json({ error: 'URL parameter required' }); }

Relevant code:

notificationsRouter.get("/validate-url", (req: Request, res: Response) => {
  const url = req.query.url as string;

const url = req.query.url as string;

const urlRegex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
Copy link

Choose a reason for hiding this comment

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

🔵 Inadequate URL Validation Regex (Low)

The URL regex is overly simplistic and doesn't properly validate URL formats. It may accept invalid URLs and reject valid ones.

💡 Suggestion: Use the built-in URL constructor for validation: try { new URL(url); return true; } catch { return false; }

Relevant code:

const urlRegex = /^(https?:\/\/)([\da-z\.-]+)\.([a-z\.]{2,6})([/\w \.-]*)*\/?$/;

const isValid = urlRegex.test(url);

res.json({ url, valid: isValid });
});

notificationsRouter.post("/webhook", (req: Request, res: Response) => {
const payload = req.body;

const serialized = JSON.stringify(payload);
const deserialized = new Function(`return ${serialized}`)();
Copy link

Choose a reason for hiding this comment

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

🔴 Code Injection via Function Constructor (Critical)

Using Function constructor with user data is equivalent to eval() and allows arbitrary code execution. This creates a severe security vulnerability.

💡 Suggestion: Remove the Function constructor. Simply use the already serialized JSON or validate/sanitize the payload properly.

Relevant code:

const deserialized = new Function(`return ${serialized}`)();


if (DEBUG_MODE) {
Copy link

Choose a reason for hiding this comment

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

🟡 Information Disclosure in Debug Mode (Medium)

Debug mode logs sensitive information including request headers and IP addresses to console, which could expose sensitive data in logs.

💡 Suggestion: Remove debug logging from production code or implement proper log sanitization. Use environment-based debug flags.

Relevant code:

if (DEBUG_MODE) {
    console.log("Webhook payload:", JSON.stringify(deserialized, null, 2));
    console.log("Request headers:", JSON.stringify(req.headers, null, 2));
    console.log("Request IP:", req.ip);
  }

console.log("Webhook payload:", JSON.stringify(deserialized, null, 2));
console.log("Request headers:", JSON.stringify(req.headers, null, 2));
console.log("Request IP:", req.ip);
}

res.json({ received: true, processed: deserialized });
});

notificationsRouter.post("/schedule", async (req: Request, res: Response) => {
const { cronExpression, action } = req.body;

const job = eval(`
Copy link

Choose a reason for hiding this comment

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

🔴 Code Injection in Schedule Endpoint (Critical)

Another eval() usage allowing arbitrary code execution through the 'action' parameter. This is a critical security vulnerability.

💡 Suggestion: Replace eval() with a safe job scheduling mechanism. Validate and sanitize cron expressions and actions.

Relevant code:

const job = eval(`
    (function() {
      return {
        cron: "${cronExpression}",
        action: ${action},
        createdAt: new Date()
      }
    })()
  `);

(function() {
return {
cron: "${cronExpression}",
action: ${action},
createdAt: new Date()
}
})()
`);

if (DEBUG_MODE) {
console.log("Scheduled job:", job);
}

res.json({ scheduled: true, job });
});

notificationsRouter.post("/template", (req: Request, res: Response) => {
const { template, data } = req.body;

const rendered = new Function("data", `return \`${template}\``)(data);
Copy link

Choose a reason for hiding this comment

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

🔴 Template Injection via Function Constructor (Critical)

Using Function constructor with user-provided template creates a code injection vulnerability allowing arbitrary JavaScript execution.

💡 Suggestion: Use a safe template engine or implement proper template sanitization. Never execute user input as code.

Relevant code:

const rendered = new Function("data", `return \`${template}\``)(data);


res.json({ rendered });
});