Skip to content
Merged
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: 1 addition & 1 deletion src/routes/certificate.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { authorizeRoles } from '../middlewares/auth/authorizeRoles';
const router = express.Router();

// Lấy certificate theo ID
router.get('/:id', isAuthenticated, (req, res, next) => {
router.get('/:id', (req, res, next) => {
getCertificateById(req, res).catch(next);
});
Comment on lines +9 to 11
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Public GET /:id can leak PII and enable ID enumeration—add validation, rate limiting, and ensure sanitized response.

If this endpoint is intentionally public, harden it:

  • Validate :id format to reject garbage/enumeration attempts.
  • Add a small per-IP rate limit.
  • Ensure getCertificateById returns only public-safe fields (no email, phone, user object, etc.). If owner-only fields exist, introduce a getPublicCertificateById serializer or gate via certificate.isPublic or a short-lived signed token.

Apply this minimal route hardening (keeps it public) and add helpers shown below:

-router.get('/:id', (req, res, next) => {
-  getCertificateById(req, res).catch(next);
-});
+router.get(
+  '/:id',
+  validateCertificateIdParam,
+  publicCertificateLimiter,
+  (req, res, next) => {
+    getCertificateById(req, res).catch(next);
+  }
+);

Add these helpers near the top of the file:

import rateLimit from 'express-rate-limit';

const publicCertificateLimiter = rateLimit({
  windowMs: 60_000,
  max: 60,
  standardHeaders: true,
  legacyHeaders: false,
});

const validateCertificateIdParam: express.RequestHandler = (req, res, next) => {
  const { id } = req.params as { id: string };
  // Adjust regex if IDs are UUIDs instead of Mongo ObjectIds
  if (!/^[0-9a-fA-F]{24}$/.test(id)) {
    return res.status(400).json({ message: 'Invalid certificate id' });
  }
  next();
};

Option (if sensitive fields exist): introduce a public-safe controller and switch the route handler:

-  (req, res, next) => {
-    getCertificateById(req, res).catch(next);
-  }
+  (req, res, next) => {
+    getPublicCertificateById(req, res).catch(next);
+  }

Verification script to confirm no PII is returned by the public handler:

I can draft a getPublicCertificateById serializer (whitelisting fields) or wire a signed-token gate if needed.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate controller implementation
rg -nP -C3 --type ts '\bgetCertificateById\b' src | sed -n '1,200p'

# Inspect for potentially sensitive fields in the response payload
rg -nP --type ts -C2 '\bres\.(json|send)\s*\(' src/controllers | sed -n '1,200p'
rg -nP --type ts -C2 '\b(email|phone|address|dob|dateOfBirth|user|profile)\b' src/controllers/certificate.controller.ts || true

# Look for populate/select patterns that might pull user data
rg -nP --type ts -C2 '\bpopulate\(|select\(|projection\b' src/controllers/certificate.controller.ts || true

Length of output: 6361


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the implementation of getCertificateById including its response handling
sed -n '1,200p' src/controllers/certificate.controller.ts

Length of output: 3459


Harden public GET /:id to prevent PII exposure and ID enumeration

The current handler

router.get('/:id', (req, res, next) => {
  getCertificateById(req, res).catch(next);
});

populates and returns the user’s email and allows unlimited requests against any ObjectId.

Please address the following critical issues:

• src/routes/certificate.route.ts (lines 9–11)
– Add a lightweight UUID/ObjectId‐format validator middleware to catch malformed IDs before invoking the controller.
– Apply a per‐IP rate limiter (e.g. 60 requests/minute) to throttle enumeration attempts.

• src/controllers/certificate.controller.ts (lines 12–16)
– Remove PII from public responses. The existing populate('user', 'name email') leaks email.
– Introduce a getPublicCertificateById that whitelists only non‐sensitive fields (e.g. certificate ID, course title, issue date), omitting email/phone/address.

Suggested diff for route hardening:

-import express from 'express';
+import express from 'express';
+import rateLimit from 'express-rate-limit';
+import { validateObjectIdParam } from '../middlewares/validateObjectIdParam';
 
 router.get('/:id', (req, res, next) => {
   getCertificateById(req, res).catch(next);
 });
 router.get(
-  '/:id',
-  (req, res, next) => {
-    getCertificateById(req, res).catch(next);
-  }
+  '/:id',
+  validateObjectIdParam('id'),
+  rateLimit({ windowMs: 60_000, max: 60 }),
+  (req, res, next) => {
+    getPublicCertificateById(req, res).catch(next);
+  }
 );

And sketch of getPublicCertificateById in src/controllers/certificate.controller.ts:

export const getPublicCertificateById = async (req: Request, res: Response) => {
  const { id } = req.params;
  // (ID format already validated by middleware)

  const cert = await Certificate.findById(id)
    .populate('course', 'title');
  if (!cert) return res.status(404).json({ message: 'Not found' });

  // Only expose non-PII fields
  const publicCert = {
    id: cert._id,
    course: cert.course.title,
    issuedAt: cert.issuedAt,
    // …other safe fields
  };

  res.status(200).json(publicCert);
};

These changes are critical to avoid leaking emails and to thwart brute-force enumeration of certificate IDs.


Expand Down