DEV Community

suhteevah
suhteevah

Posted on

Every Input Is an Attack Vector: A Developer's Guide to Input Validation

Every form field, query parameter, URL slug, file upload, and HTTP header your application accepts is an attack surface. If you're not validating and sanitizing all of them, you have vulnerabilities. This isn't a question of "if" — it's a question of how many.

We have linters for code style, type checkers for safety, test frameworks for correctness. But input validation? Most teams rely on frameworks to handle it, and frameworks only cover the happy path.

I built InputShield to scan for input validation failures that standard linting tools miss. Here are the 6 most dangerous patterns it catches.

1. SQL Injection — Still Alive in 2026

ORMs handle most queries. But there's always that one raw query for a complex join or a search feature.

// The pattern — string concatenation in SQL
app.get('/search', (req, res) => {
  const query = \`SELECT * FROM products WHERE name LIKE '%${req.query.q}%'\`;
  db.query(query); // SQL injection
});

// The fix — parameterized queries, always
app.get('/search', (req, res) => {
  db.query('SELECT * FROM products WHERE name LIKE $1', [\`%${req.query.q}%\`]);
});
Enter fullscreen mode Exit fullscreen mode

InputShield rule SQ-001 catches string concatenation in SQL queries. SQ-004 flags template literals in raw SQL calls. SQ-007 finds ORM raw queries with unparameterized input.

2. Cross-Site Scripting (XSS) — More Vectors Than You Think

It's not just innerHTML. There's dangerouslySetInnerHTML in React, v-html in Vue, [innerHTML] in Angular, document.write, and href attributes with javascript:\ protocol.

// A comment component that renders "processed" markdown
function Comment({ content }) {
  const html = markdownToHtml(content); // Does this sanitize? Usually no.
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

// The fix — sanitize before rendering
import DOMPurify from 'dompurify';
function Comment({ content }) {
  const html = DOMPurify.sanitize(markdownToHtml(content));
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
Enter fullscreen mode Exit fullscreen mode

InputShield rule XS-001 flags innerHTML from untrusted sources. XS-003 catches dangerouslySetInnerHTML with unsanitized data. XS-007 detects href attributes with user-controlled values.

3. Command Injection — The exec() Problem

File conversion, image processing, PDF generation — anything that shells out to a system command is a risk if user input reaches it.

// A file conversion endpoint
app.post('/convert', (req, res) => {
  const { filename } = req.body;
  exec(\`convert ${filename} output.pdf\`); // Command injection
});

// The fix — use execFile with explicit arguments
const { execFile } = require('child_process');
app.post('/convert', (req, res) => {
  const safeName = path.basename(filename); // Strip path traversal
  execFile('convert', [safeName, 'output.pdf']); // No shell interpolation
});
Enter fullscreen mode Exit fullscreen mode

InputShield rule CI-001 catches user input in exec() calls. CI-004 flags unsanitized strings in child_process.spawn. CI-008 detects commands built from request parameters.

4. Path Traversal — When Users Control File Paths

// A file download endpoint
app.get('/download', (req, res) => {
  const file = req.query.file;
  res.sendFile(path.join('/uploads', file));
  // GET /download?file=../../etc/passwd — game over
});

// The fix — resolve and verify
app.get('/download', (req, res) => {
  const file = path.basename(req.query.file); // Strip directory traversal
  const fullPath = path.resolve('/uploads', file);
  if (!fullPath.startsWith('/uploads/')) {
    return res.status(403).send('Forbidden');
  }
  res.sendFile(fullPath);
});
Enter fullscreen mode Exit fullscreen mode

InputShield rule PT-001 catches user input in fs.readFile paths. PT-004 flags path.join with user-controlled segments. PT-007 detects directory traversal sequences in file operations.

5. Server-Side Request Forgery (SSRF)

// A URL preview feature
app.post('/preview', async (req, res) => {
  const { url } = req.body;
  const response = await fetch(url); // SSRF — user controls the URL
  const html = await response.text();
  res.json({ preview: extractMeta(html) });
});
Enter fullscreen mode Exit fullscreen mode

An attacker sends url=http://169.254.169.254/latest/meta-data/\ and reads your AWS instance metadata, including IAM credentials.

The fix: validate the URL against an allowlist of domains, block internal IP ranges, and use a DNS resolver that prevents rebinding attacks.

InputShield rule SS-001 catches user-controlled URLs in server-side fetch calls. SS-004 flags missing URL validation before HTTP requests.

6. ReDoS — Regular Expressions as Attack Vectors

// A search endpoint that builds regex from user input
app.get('/search', (req, res) => {
  const pattern = new RegExp(req.query.q); // ReDoS risk
  const results = items.filter(item => pattern.test(item.name));
  res.json(results);
});
Enter fullscreen mode Exit fullscreen mode

A crafted input like (a+)+$\ causes catastrophic backtracking. Your server hangs on a single regex evaluation.

The fix: never build regex from user input. Use string methods (includes, indexOf) or a safe regex library with timeouts.

InputShield rule RE-001 catches regex constructed from user input. RE-004 flags patterns with potential catastrophic backtracking applied to user data.

Run It On Your Codebase

InputShield scans for all 6 of these categories (90 patterns total) in one pass:

clawhub install inputshield
inputshield scan .
Enter fullscreen mode Exit fullscreen mode

Example output:

$ inputshield scan src/

[CRITICAL] CI-002 Unsanitized exec() with user input — api/convert.js:31
[CRITICAL] SQ-001 SQL query with string concatenation — db/users.js:55
[HIGH]     XS-001 innerHTML from untrusted source — components/comment.tsx:18
[HIGH]     PT-004 path.join with user-controlled segment — api/files.js:42

Score: 38/100 (Grade: F)
Enter fullscreen mode Exit fullscreen mode

The average web application I've scanned scores 38/100. Run it on your codebase and see where you land.

Free to scan. Pro ($19/mo) adds pre-commit hooks so injection vectors can't merge. Runs 100% locally — your source code stays on your machine.

InputShield | GitHub

Top comments (0)