PS Product SecurityKnowledge Base

๐ŸŸจ JavaScript Vulnerability Examples and Fixes

Use this page when: reviewing Node/Express backends, admin dashboards, browser-heavy workflows, and old JavaScript where โ€œjust one helperโ€ quietly became part of the trust boundary.

How to read these examples

  • Vulnerable snippet shows the unsafe habit.
  • Safer pattern shows the direction you want in production code.
  • Why it matters ties the defect to attacker value and business impact.
  • Review cue is phrased so it can become a pull-request comment or checklist item.

Example 1 โ€” DOM XSS via innerHTML

Vulnerable snippet

const params = new URLSearchParams(location.search);
document.getElementById('welcome').innerHTML = params.get('name');

Safer pattern

const params = new URLSearchParams(location.search);
document.getElementById('welcome').textContent = params.get('name') || '';

Why it matters

  • innerHTML treats attacker-controlled content as markup and script-capable DOM input.

Business impact

  • Session theft, account takeover, malicious actions in the victim session, and browser-side trust collapse.

Review cue

  • Prefer textContent for plain text. Use trusted sanitization only when rich HTML is actually required.

Example 2 โ€” NoSQL injection in login flow

Vulnerable snippet

app.post('/login', async (req, res) => {
  const user = await db.collection('users').findOne({
    email: req.body.email,
    password: req.body.password
  });
  if (user) res.sendStatus(200);
});

Safer pattern

app.post('/login', async (req, res) => {
  const email = String(req.body.email || '');
  const password = String(req.body.password || '');
  const user = await db.collection('users').findOne({ email });
  if (!user) return res.sendStatus(401);
  const ok = await bcrypt.compare(password, user.passwordHash);
  if (!ok) return res.sendStatus(401);
  res.sendStatus(200);
});

Why it matters

  • When request bodies are used as query objects or compared directly, attacker-crafted structures can alter query meaning.

Business impact

  • Authentication bypass, user enumeration, and exposure of account existence or account takeover paths.

Review cue

  • Cast request fields to expected scalar types and separate identity lookup from password verification.

Example 3 โ€” Path traversal in file download endpoint

Vulnerable snippet

app.get('/download', (req, res) => {
  const filePath = '/srv/public/' + req.query.name;
  res.sendFile(filePath);
});

Safer pattern

const path = require('node:path');
app.get('/download', (req, res) => {
  const base = '/srv/public';
  const target = path.resolve(base, String(req.query.name || ''));
  if (!target.startsWith(path.resolve(base) + path.sep)) {
    return res.status(400).send('invalid path');
  }
  res.sendFile(target);
});

Why it matters

  • Concatenating file paths allows ../ traversal or unexpected absolute paths to escape the intended directory.

Business impact

  • Disclosure of secrets, source code, configs, keys, or operational documents on the host.

Review cue

  • Normalize and resolve paths against a fixed base, then verify the resolved prefix before serving a file.

Example 4 โ€” JWT misuse by decoding without verification

Vulnerable snippet

app.get('/admin', (req, res) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  const claims = jwt.decode(token);
  if (claims?.role === 'admin') return res.send('ok');
  res.sendStatus(403);
});

Safer pattern

app.get('/admin', (req, res) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  try {
    const claims = jwt.verify(token, PUBLIC_KEY, { algorithms: ['RS256'] });
    if (claims.role !== 'admin') return res.sendStatus(403);
    return res.send('ok');
  } catch {
    return res.sendStatus(401);
  }
});

Why it matters

  • decode() reads claims but does not prove signature, issuer, audience, or token validity.

Business impact

  • Privilege escalation, admin impersonation, and broken trust in every downstream authorization decision.

Review cue

  • Only verified claims may influence authorization. Require explicit algorithm and key expectations.

Example 5 โ€” Prototype pollution through unsafe deep merge

Vulnerable snippet

const settings = merge(defaultSettings, req.body);
// later logic trusts merged object

Safer pattern

const allowed = (({ theme, locale, pageSize }) => ({ theme, locale, pageSize }))(req.body || {});
const settings = { ...defaultSettings, ...allowed };

Why it matters

  • Deep-merging untrusted objects can modify inherited properties or unexpected control flags used later in the application.

Business impact

  • Authorization confusion, logic corruption, denial of service, and hard-to-debug cross-request behavior.

Review cue

  • Do not deep-merge untrusted objects into trusted configuration; use allowlisted fields and plain object construction.

Author attribution: Ivan Piskunov, 2026 - Educational and defensive-engineering use.