Skip to content

Validation Security — Injection Prevention, XSS, and Sanitization

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you'll learn about Validation Security. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Validation security ensures that input processing does not introduce vulnerabilities. Validation rejects invalid data, while sanitization removes dangerous content from valid data.

What You'll Learn

By the end of this lesson, you will distinguish validation from sanitization, prevent injection attacks through input handling, protect against XSS via data content, and build a secure input pipeline.

Why It Matters

Validation alone cannot stop all attacks. SQL injection, XSS, Command Injection, and NoSQL injection can all be delivered through syntactically valid data. A secure input pipeline must validate first, then sanitize.

Real-World Use

Durga Antivirus Pro validates file uploads by type and size, then sanitizes filenames to prevent path traversal attacks before writing to disk. Both layers are required.

Validation vs Sanitization Flow

flowchart LR
    Input[Raw Input] --> Validate{Validation}
    Validate -->|Invalid| Reject[Reject with 400]
    Validate -->|Valid| Sanitize[Sanitization]
    Sanitize --> Strip[Strip Dangerous Content]
    Strip --> Escape[Escape for Context]
    Escape --> Safe[Safe Output]
    Safe --> DB[Database]
    Safe --> Render[HTML Render]
    Safe --> Shell[Shell Command]

SQL Injection Prevention via Validation

# sql_injection_prevention.py
import re
from typing import Any, Dict, List, Optional

class SQLInjectionPrevention:
    DANGEROUS_PATTERNS = [
        r"['\";]",           # quotes and semicolons
        r"--",               # SQL comment
        r"/\*",              # block comment open
        r"UNION\s+ALL",      # UNION attacks
        r"UNION\s+SELECT",   # UNION SELECT
        r"OR\s+1\s*=\s*1",   # tautology
        r"OR\s+'1'\s*=\s*'1'",
        r"DROP\s+TABLE",     # DDL
        r"DELETE\s+FROM",    # DML
        r"INSERT\s+INTO",
        r"EXEC\(",           # SQL Server exec
        r"xp_cmdshell",      # extended stored proc
        r"pg_sleep",         # PostgreSQL time-based
        r"BENCHMARK\s*\(",   # MySQL time-based
    ]

    @staticmethod
    def contains_sql_injection(value: str) -> bool:
        if not isinstance(value, str):
            return False
        upper = value.upper()
        for pattern in SQLInjectionPrevention.DANGEROUS_PATTERNS:
            if re.search(pattern, upper):
                return True
        return False

    @staticmethod
    def safe_username(value: str) -> Optional[str]:
        if not isinstance(value, str):
            return "Username must be a string"
        if SQLInjectionPrevention.contains_sql_injection(value):
            return "Username contains invalid characters"
        if not re.match(r'^[a-zA-Z0-9_]{3,30}$', value):
            return "Username: 3-30 alphanumeric characters or underscores"
        return None

    @staticmethod
    def safe_search_term(value: str) -> Optional[str]:
        if not isinstance(value, str):
            return "Search term must be a string"
        if SQLInjectionPrevention.contains_sql_injection(value):
            return "Search term contains invalid characters"
        if len(value) > 200:
            return "Search term too long"
        return None

sp = SQLInjectionPrevention()
tests = [
    ("admin' --", sp.safe_username),
    ("Robert'); DROP TABLE Students;--", sp.safe_username),
    ("alice_dev", sp.safe_username),
    ("1 OR 1=1", sp.safe_search_term),
    ("safe search query", sp.safe_search_term),
]

for value, validator in tests:
    error = validator(value)
    status = "VALID" if not error else error
    print(f"  {str(value):45s} -> {status}")

Expected output:

  admin' --                                      -> Username contains invalid characters
  Robert'); DROP TABLE Students;--              -> Username contains invalid characters
  alice_dev                                      -> VALID
  1 OR 1=1                                       -> Search term contains invalid characters
  safe search query                              -> VALID

XSS Prevention in Validation

# xss_prevention.py
import re
from html import escape
from typing import Any, Dict, List, Optional

class XSSPrevention:
    DANGEROUS_TAGS = [
        r"<script[^>]*>",
        r"<iframe[^>]*>",
        r"<object[^>]*>",
        r"<embed[^>]*>",
        r"<svg[^>]*onload",
        r"<img[^>]*onerror",
        r"<img[^>]*onload",
        r"<[^>]*onclick",
        r"<[^>]*onmouseover",
        r"<[^>]*onfocus",
        r"<[^>]*onchange",
        r"<[^>]*onsubmit",
        r"javascript:",
        r"onerror\s*=",
        r"onload\s*=",
        r"expression\s*\(",
    ]

    @staticmethod
    def contains_xss(value: str) -> bool:
        if not isinstance(value, str):
            return False
        lower = value.lower()
        for pattern in XSSPrevention.DANGEROUS_TAGS:
            if re.search(pattern, lower):
                return True
        return False

    @staticmethod
    def sanitize_html(value: str) -> str:
        return escape(value, quote=True)

    @staticmethod
    def sanitize_url(value: str) -> Optional[str]:
        allowed_schemes = ["http", "https", "mailto", "tel"]
        lower = value.lower().strip()
        for scheme in allowed_schemes:
            if lower.startswith(scheme + ":"):
                return value
        if ":" in lower and not lower.startswith("/"):
            return None
        return value

    @staticmethod
    def validate_comment(value: str) -> Optional[str]:
        if not isinstance(value, str):
            return "Comment must be a string"
        if XSSPrevention.contains_xss(value):
            return "Comment contains disallowed HTML"
        if len(value) > 1000:
            return "Comment too long (max 1000 chars)"
        return None

xp = XSSPrevention()
test_inputs = [
    "Hello, this is a safe comment!",
    "<script>alert('xss')</script>",
    "<img src=x onerror=alert(1)>",
    "<a href=\"javascript:alert(1)\">click</a>",
    "Safe URL: https://example.com",
]

for inp in test_inputs:
    error = xp.validate_comment(inp)
    status = "VALID" if not error else error
    sanitized = xp.sanitize_html(inp)
    print(f"  Input:    {inp[:50]:50s}")
    print(f"  Status:   {status}")
    print(f"  Sanitized: {sanitized[:50]:50s}")
    print()

Expected output:

  Input:    Hello, this is a safe comment!
  Status:   VALID
  Sanitized: Hello, this is a safe comment!

  Input:    <script>alert('xss')</script>
  Status:   Comment contains disallowed HTML
  Sanitized: &lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt;

  Input:    <img src=x onerror=alert(1)>
  Status:   Comment contains disallowed HTML
  Sanitized: &lt;img src=x onerror=alert(1)&gt;

  Input:    <a href=\"javascript:alert(1)\">click</a>
  Status:   Comment contains disallowed HTML
  Sanitized: &lt;a href=&quot;javascript:alert(1)&quot;&gt;click&lt;/a&gt;

  Input:    Safe URL: https://example.com
  Status:   VALID
  Sanitized: Safe URL: https://example.com

Command Injection Prevention

# command_injection.py
import re
import shlex
from typing import Optional

class CommandInjectionPrevention:
    DANGEROUS_CHARS = r'[;&|`$(){}[\]!#~<>]'
    DANGEROUS_COMMANDS = [
        "rm", "sudo", "eval", "exec", "system(", "passthru",
        "shell_exec", "popen", "curl", "wget", "bash",
        "sh -c", "cmd.exe", "powershell",
    ]

    @staticmethod
    def is_safe_filename(value: str) -> Optional[str]:
        if not isinstance(value, str):
            return "Filename must be a string"
        if re.search(CommandInjectionPrevention.DANGEROUS_CHARS, value):
            return "Filename contains invalid characters"
        if value.strip() == "" or value.startswith("."):
            return "Invalid filename"
        if len(value) > 255:
            return "Filename too long"
        return None

    @staticmethod
    def is_safe_path_component(value: str) -> Optional[str]:
        if not isinstance(value, str):
            return "Path component must be a string"
        if re.search(r'[\\/]', value):
            return "Path component cannot contain slashes"
        if value == ".." or value == ".":
            return "Invalid path component"
        return None

    @staticmethod
    def sanitize_filename(value: str) -> str:
        safe = re.sub(r'[^a-zA-Z0-9._-]', '_', value)
        safe = re.sub(r'_{2,}', '_', safe)
        return safe.strip('_.')

cip = CommandInjectionPrevention()
test_names = [
    "report.pdf",
    "file; rm -rf /",
    "`cat /etc/passwd`",
    "../etc/passwd",
    "file$(whoami).txt",
    "valid-script-1.0.sh",
]

for name in test_names:
    error = cip.is_safe_filename(name)
    sanitized = cip.sanitize_filename(name)
    print(f"  Original:  {name:30s}")
    print(f"  Safe:      {'YES' if not error else 'NO'}")
    if error:
        print(f"  Error:     {error}")
    print(f"  Sanitized: {sanitized}")
    print()

Expected output:

  Original:  report.pdf
  Safe:      YES
  Sanitized: report.pdf

  Original:  file; rm -rf /
  Safe:      NO
  Error:     Filename contains invalid characters
  Sanitized: file__rm_-rf_

  Original:  `cat /etc/passwd`
  Safe:      NO
  Error:     Filename contains invalid characters
  Sanitized: _cat__etc_passwd_

  Original:  ../etc/passwd
  Safe:      NO
  Error:     Filename contains invalid characters
  Sanitized: __etc_passwd

  Original:  file$(whoami).txt
  Safe:      NO
  Error:     Filename contains invalid characters
  Sanitized: file_whoami_.txt

  Original:  valid-script-1.0.sh
  Safe:      YES
  Sanitized: valid-script-1.0.sh

Secure Input Pipeline

# secure_input_pipeline.py
from typing import Any, Callable, Dict, List, Optional, Tuple

class PipelineStage:
    def __init__(self, name: str, process: Callable[[Any], Tuple[bool, Any, Optional[str]]]):
        self.name = name
        self.process = process

class SecureInputPipeline:
    def __init__(self):
        self.stages: List[PipelineStage] = []

    def add(self, stage: PipelineStage):
        self.stages.append(stage)

    def process(self, data: Any) -> Dict:
        current = data
        for stage in self.stages:
            success, result, error = stage.process(current)
            if not success:
                return {"success": False, "stage": stage.name, "error": error, "data": current}
            current = result
        return {"success": True, "stage": "complete", "error": None, "data": current}

pipeline = SecureInputPipeline()

pipeline.add(PipelineStage("type_check", lambda v: (isinstance(v, str), v, None if isinstance(v, str) else "Must be string")))
pipeline.add(PipelineStage("length_check", lambda v: (True, v, None) if 1 <= len(v) <= 100 else (False, v, "Length must be 1-100")))
pipeline.add(PipelineStage("sql_injection_check", lambda v: (False, v, "SQL injection detected") if re.search(r"['\"--]", v) else (True, v, None)))
pipeline.add(PipelineStage("xss_check", lambda v: (False, v, "XSS detected") if re.search(r"<script|<img.*onerror", v, re.I) else (True, v, None)))
pipeline.add(PipelineStage("trim", lambda v: (True, v.strip(), None)))

import re
print(pipeline.process("Hello world"))
print(pipeline.process("<script>alert(1)</script>"))
print(pipeline.process("Robert'; DROP TABLE Students;--"))
print(pipeline.process(""))
print(pipeline.process(12345))

Expected output:

{'success': True, 'stage': 'complete', 'error': None, 'data': 'Hello world'}
{'success': False, 'stage': 'xss_check', 'error': 'XSS detected', 'data': '<script>alert(1)</script>'}
{'success': False, 'stage': 'sql_injection_check', 'error': 'SQL injection detected', 'data': "Robert'; DROP TABLE Students;--"}
{'success': False, 'stage': 'length_check', 'error': 'Length must be 1-100', 'data': ''}
{'success': False, 'stage': 'type_check', 'error': 'Must be string', 'data': 12345}

Common Mistakes

1. Confusing Validation with Sanitization

Validation rejects bad data. Sanitization cleans good data. Doing only one leaves gaps. Always do both.

2. Blacklisting Instead of Whitelisting

Blocking specific dangerous patterns is fragile. Attackers find new patterns. Whitelist allowed characters and structures instead.

3. Not Contextualizing Output Encoding

HTML-encoding for the database does not stop XSS in HTML output. Encode for each context: HTML, URL, JavaScript, CSS.

4. Relying on Client-Side Security Only

JavaScript validation is invisible to attackers. All security validation must happen on the server.

5. Not Handling NoSQL Injection

MongoDB $where, $gt, and $ne operators can be injected through JSON input. Validate types and reject operator keys.

Practice Questions

1. What is the difference between validation and sanitization?

Validation rejects invalid input. Sanitization removes or escapes dangerous parts of valid input. Both are required.

2. How do you prevent SQL injection in input?

Use parameterized queries. Also validate input for SQL metacharacters as a defense-in-depth layer.

3. What is XSS and how do you prevent it via input handling?

XSS injects JavaScript into web pages. Validate to block script tags, sanitize with HTML escaping (html.escape), and use CSP headers.

4. Why is whitelisting better than blacklisting for security?

Blacklists need constant updates. Whitelists define exactly what is allowed and reject everything else.

Challenge

Build a secure input pipeline for a file upload API: validate MIME type, sanitize filename (path traversal, command injection), scan for malware signatures, check file size, and store with a safe randomly-generated filename.

FAQ

What is the difference between validation and sanitization?

Validation checks if data is correct. Sanitization removes dangerous content. Validation comes first, then sanitization for safe output.

Can validation prevent SQL injection?

Input validation is a defense layer, but parameterized queries are the primary defense. Never rely on validation alone.

What is NoSQL injection?

Injecting MongoDB operators like $gt, $ne, $where through JSON input to bypass authentication or extract data.

Should I sanitize input before validation?

No. Validate the original input first, then sanitize for storage or display. Sanitization can mask injection attempts.

What is command injection?

Injecting shell commands through input fields that are passed to system(), exec(), or subprocess without escaping.

Mini Project: Secure Input Handler

# secure_input_handler.py
import re
from typing import Any, Dict, List, Optional, Tuple

class SecureInputHandler:
    def __init__(self):
        self.validators: List[callable] = []
        self.sanitizers: List[callable] = []

    def add_validator(self, fn: callable):
        self.validators.append(fn)

    def add_sanitizer(self, fn: callable):
        self.sanitizers.append(fn)

    def process(self, value: Any) -> Tuple[bool, Any, Optional[str]]:
        for validator in self.validators:
            error = validator(value)
            if error:
                return False, value, error
        for sanitizer in self.sanitizers:
            value = sanitizer(value)
        return True, value, None

handler = SecureInputHandler()
handler.add_validator(lambda v: None if isinstance(v, str) else "Must be string")
handler.add_validator(lambda v: None if len(v) <= 500 else "Too long")
handler.add_sanitizer(lambda v: v.strip())
handler.add_sanitizer(lambda v: re.sub(r'<[^>]*>', '', v))

tests = ["<b>Hello</b>", "<script>alert(1)</script>", 123, "A" * 600]
for test in tests:
    valid, result, error = handler.process(test)
    print(f"  Input: {str(test)[:30]:30s} Valid: {valid} Result: {str(result)[:30]:30s} Error: {error}")

Expected output:

  Input: <b>Hello</b>                Valid: True Result: Hello                         Error: None
  Input: <script>alert(1)</script>   Valid: True Result: alert(1)                      Error: None
  Input: 123                          Valid: False Result: 123                          Error: Must be string
  Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Valid: False Result: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Error: Too long

What's Next

You understand validation security. Next, learn honeypot validation and bot detection, then data validation complete project.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro