Skip to content

CWE-90: LDAP Injection - JavaScript

Overview

LDAP Injection in JavaScript/Node.js applications occurs when untrusted user input is used to construct LDAP queries without proper validation or escaping. LDAP (Lightweight Directory Access Protocol) is commonly used in enterprise applications for authentication and directory services. Attackers can manipulate LDAP queries to bypass authentication, escalate privileges, extract sensitive directory data, or cause denial of service.

Primary Defence: Use safe filter builders or explicit RFC 4515 escaping before inserting user input into filters, validate all input against strict allowlists (alphanumeric and safe characters only), never allow wildcards (*) in authentication queries, and use bind authentication instead of search-based authentication to prevent LDAP injection attacks.

Common JavaScript LDAP Vulnerability Scenarios:

  • Building LDAP search filters with string concatenation or template literals
  • Using user input directly in Distinguished Names (DNs)
  • Accepting wildcards in authentication queries
  • Insufficient validation of special LDAP characters
  • Information disclosure via LDAP error messages

Popular Node.js LDAP Libraries:

  • ldapjs: Full-featured LDAP client and server
  • passport-ldapauth: Passport strategy for LDAP authentication
  • activedirectory2: Active Directory integration
  • ldapts: Modern LDAP client with TypeScript support
  • simple-ldap-search: Simplified LDAP search wrapper

LDAP Special Characters: RFC 4515 requires escaping: *, (, ), \, NUL (some codebases also escape / as defense in depth)

Common Vulnerable Patterns

String Concatenation in ldapjs

// VULNERABLE - Direct string concatenation with ldapjs
const ldap = require('ldapjs');

function authenticateUser(username, password, callback) {
    const client = ldap.createClient({
        url: 'ldap://localhost:389'
    });

    // Bind as service account
    client.bind('cn=admin,dc=example,dc=com', 'admin_password', (err) => {
        if (err) {
            return callback(err);
        }

        // VULNERABLE - User input directly in search filter
        const searchFilter = `(uid=${username})`;

        const opts = {
            filter: searchFilter,
            scope: 'sub',
            attributes: ['dn']
        };

        client.search('ou=users,dc=example,dc=com', opts, (err, res) => {
            if (err) {
                return callback(err);
            }

            let userDn = null;

            res.on('searchEntry', (entry) => {
                userDn = entry.dn.toString(); // Older ldapjs: entry.objectName
            });

            res.on('end', () => {
                if (!userDn) {
                    return callback(new Error('User not found'));
                }

                // Authenticate as user
                const userClient = ldap.createClient({
                    url: 'ldap://localhost:389'
                });

                userClient.bind(userDn, password, (err) => {
                    callback(err, !err);
                    userClient.unbind();
                });

                client.unbind();
            });
        });
    });
}

// Attack: username = "admin)(&" bypasses authentication
// Resulting filter: (uid=admin)(&)
// Matches ANY user with uid=admin

Why this is vulnerable:

  • Template literals allow injection
  • Special characters )( manipulate filter logic
  • No input validation or escaping
  • Attackers can craft filters to bypass authentication

Express.js REST API Without Sanitization

// VULNERABLE - Express REST API with LDAP
const express = require('express');
const ldap = require('ldapjs');
const app = express();

app.use(express.json());

app.get('/api/users/search', (req, res) => {
    const { name } = req.query;

    const client = ldap.createClient({
        url: 'ldap://ldap.example.com:389'
    });

    client.bind('cn=readonly,dc=example,dc=com', 'readonly_password', (err) => {
        if (err) {
            return res.status(500).json({ error: err.message });
        }

        // VULNERABLE - Unsanitized input in LDAP filter
        const searchFilter = `(cn=*${name}*)`;

        const opts = {
            filter: searchFilter,
            scope: 'sub',
            attributes: ['cn', 'mail']
        };

        const users = [];

        client.search('ou=people,dc=example,dc=com', opts, (err, searchRes) => {
            if (err) {
                // VULNERABLE - Detailed error message
                return res.status(500).json({ error: `LDAP search failed: ${err.message}` });
            }

            searchRes.on('searchEntry', (entry) => {
                users.push({
                    name: entry.object.cn,
                    email: entry.object.mail
                });
            });

            searchRes.on('end', () => {
                res.json({ users });
                client.unbind();
            });
        });
    });
});

app.listen(3000);

Why this is vulnerable:

  • Query parameters directly in filter
  • Wildcard search accepts any input
  • Detailed error messages leak information
  • No character filtering

Passport-ldapauth Without Escaping

// VULNERABLE - Passport LDAP authentication
const passport = require('passport');
const LdapStrategy = require('passport-ldapauth');

// VULNERABLE - User input in search filter
passport.use(new LdapStrategy({
    server: {
        url: 'ldap://localhost:389',
        bindDN: 'cn=admin,dc=example,dc=com',
        bindCredentials: 'admin_password',
        searchBase: 'ou=users,dc=example,dc=com',
        searchFilter: '(uid={{username}})'  // VULNERABLE!
    }
}, (user, done) => {
    return done(null, user);
}));

app.post('/login', passport.authenticate('ldapauth', {
    successRedirect: '/dashboard',
    failureRedirect: '/login'
}));

Why this is vulnerable:

  • Template variable {{username}} doesn't escape
  • passport-ldapauth doesn't sanitize by default
  • Injection possible with special characters

DN Injection

// VULNERABLE - User input in Distinguished Name
const ldap = require('ldapjs');

function getUserInfo(orgUnit, userId, callback) {
    const client = ldap.createClient({
        url: 'ldap://localhost:389'
    });

    // VULNERABLE - User input in DN construction
    const userDn = `uid=${userId},ou=${orgUnit},dc=example,dc=com`;

    client.bind('cn=admin,dc=example,dc=com', 'admin_password', (err) => {
        if (err) {
            return callback(err);
        }

        const opts = {
            scope: 'base',
            attributes: ['cn', 'mail']
        };

        client.search(userDn, opts, (err, res) => {
            // Process results...
        });
    });
}

// Attack: orgUnit = "users,dc=example,dc=com)(uid=admin"
// Allows access to different user's data

Why this is vulnerable:

  • DN components not validated
  • DN escaping requirements different from filter escaping
  • Privilege escalation possible
  • Information disclosure

Active Directory Without Sanitization

// VULNERABLE - Active Directory search
const ActiveDirectory = require('activedirectory2');

const config = {
    url: 'ldap://ad.example.com',
    baseDN: 'dc=example,dc=com',
    username: 'admin@example.com',
    password: 'admin_password'
};

const ad = new ActiveDirectory(config);

function searchUsers(searchTerm, callback) {
    // VULNERABLE - User input in search query
    const query = `cn=${searchTerm}*`;

    ad.findUsers(query, (err, users) => {
        if (err) {
            return callback(err);
        }
        callback(null, users);
    });
}

Why this is vulnerable:

  • activedirectory2 library doesn't auto-escape string queries
  • Wildcard injection possible
  • Information enumeration
  • No input validation

TypeScript with ldapts

// VULNERABLE - TypeScript with ldapts
import { Client } from 'ldapts';

async function authenticate(username: string, password: string): Promise<boolean> {
    const client = new Client({
        url: 'ldap://localhost:389'
    });

    try {
        await client.bind('cn=admin,dc=example,dc=com', 'admin_password');

        // VULNERABLE - Template literal injection
        const { searchEntries } = await client.search('ou=users,dc=example,dc=com', {
            filter: `(uid=${username})`,
            scope: 'sub',
            attributes: ['dn']
        });

        if (searchEntries.length === 0) {
            return false;
        }

        const userDn = searchEntries[0].dn;

        // Try to authenticate
        const userClient = new Client({ url: 'ldap://localhost:389' });
        await userClient.bind(userDn, password);
        await userClient.unbind();

        return true;
    } catch (err) {
        return false;
    } finally {
        await client.unbind();
    }
}

Why this is vulnerable:

  • TypeScript doesn't prevent injection
  • Template literals allow special characters
  • Modern library doesn't auto-escape
  • False sense of security

Next.js API Route

// VULNERABLE - Next.js API route with LDAP
// pages/api/users/search.js
import ldap from 'ldapjs';

export default function handler(req, res) {
    const { query } = req.query;

    const client = ldap.createClient({
        url: process.env.LDAP_URL
    });

    client.bind(process.env.LDAP_BIND_DN, process.env.LDAP_PASSWORD, (err) => {
        if (err) {
            return res.status(500).json({ error: 'Authentication failed' });
        }

        // VULNERABLE - Query parameter in filter
        const filter = `(cn=*${query}*)`;

        const opts = {
            filter: filter,
            scope: 'sub',
            attributes: ['cn', 'mail']
        };

        const results = [];

        client.search(process.env.LDAP_BASE_DN, opts, (err, search) => {
            search.on('searchEntry', (entry) => {
                results.push(entry.object);
            });

            search.on('end', () => {
                res.status(200).json({ results });
                client.unbind();
            });

            search.on('error', (err) => {
                res.status(500).json({ error: err.message });
            });
        });
    });
}

Why this is vulnerable:

  • Next.js API route exposes injection point
  • No validation on query parameters
  • Environment variables don't prevent injection
  • Error messages leak details

Wildcard Injection

// VULNERABLE - Accepting wildcards from users
const ldap = require('ldapjs');

function searchByPattern(pattern, callback) {
    const client = ldap.createClient({
        url: 'ldap://localhost:389'
    });

    client.bind('cn=admin,dc=example,dc=com', 'password', (err) => {
        if (err) {
            return callback(err);
        }

        // VULNERABLE - Wildcard search with user input
        const filter = `(cn=${pattern})`;

        const opts = {
            filter: filter,
            scope: 'sub',
            attributes: ['cn']
        };

        const results = [];

        client.search('ou=people,dc=example,dc=com', opts, (err, res) => {
            res.on('searchEntry', (entry) => {
                results.push(entry.object);
            });

            res.on('end', () => {
                callback(null, results);
                client.unbind();
            });
        });
    });
}

// Attack: pattern = "*" returns ALL entries
// Attack: pattern = "a*)(objectClass=*" performs OR injection

Why this is vulnerable:

  • Wildcards allow enumeration attacks
  • Can extract entire directory
  • Denial of service via resource exhaustion
  • Information leakage

Secure Patterns

ldapjs with Escaping Function

// SECURE - ldapjs with proper escaping
const ldap = require('ldapjs');

/**
 * Escape LDAP search filter special characters
 * @param {string} str - Input string to escape
 * @returns {string} - Escaped string safe for LDAP filters
 */
function escapeLDAPFilter(str) {
    if (typeof str !== 'string') {
        throw new TypeError('Input must be a string');
    }

    return str
        .replace(/\\/g, '\\5c')   // Backslash
        .replace(/\*/g, '\\2a')   // Asterisk
        .replace(/\(/g, '\\28')   // Left parenthesis
        .replace(/\)/g, '\\29')   // Right parenthesis
        .replace(/\x00/g, '\\00') // NULL
        .replace(/\//g, '\\2f');  // Optional: not required by RFC 4515
}

/**
 * Validate username format
 * @param {string} username
 * @returns {boolean}
 */
function isValidUsername(username) {
    return /^[a-zA-Z0-9._-]{3,64}$/.test(username);
}

async function authenticateUser(username, password) {
    // SECURE - Validate input format
    if (!isValidUsername(username)) {
        throw new Error('Invalid username format');
    }

    if (!password || password.length > 128) {
        throw new Error('Invalid password');
    }

    const client = ldap.createClient({
        url: 'ldap://localhost:389',
        timeout: 5000
    });

    return new Promise((resolve, reject) => {
        client.bind('cn=service,dc=example,dc=com', 'service_password', (err) => {
            if (err) {
                return reject(err);
            }

            // SECURE - Escape special characters
            const safeUsername = escapeLDAPFilter(username);
            const searchFilter = `(&(objectClass=person)(uid=${safeUsername}))`;

            const opts = {
                filter: searchFilter,
                scope: 'sub',
                attributes: ['dn'],
                sizeLimit: 1
            };

            client.search('ou=users,dc=example,dc=com', opts, (err, res) => {
                if (err) {
                    client.unbind();
                    return reject(err);
                }

                let userDn = null;

                res.on('searchEntry', (entry) => {
                    userDn = entry.objectName;
                });

                res.on('end', () => {
                    if (!userDn) {
                        client.unbind();
                        return resolve(false);
                    }

                    // SECURE - Authenticate as user
                    const userClient = ldap.createClient({
                        url: 'ldap://localhost:389'
                    });

                    userClient.bind(userDn, password, (err) => {
                        userClient.unbind();
                        client.unbind();

                        if (err) {
                            return resolve(false);
                        }
                        resolve(true);
                    });
                });

                res.on('error', (err) => {
                    client.unbind();
                    reject(err);
                });
            });
        });
    });
}

module.exports = { authenticateUser, escapeLDAPFilter };

Why this works:

  • Manual LDAP filter escaping replaces special characters with backslash-hex escape sequences following RFC 4515, preventing filter injection.
  • The key characters are \ (backslash itself, must be escaped first), * (wildcard), ( and ) (filter operators), and \x00 (NUL byte).
  • Each is converted to its hex representation (e.g., * becomes \2a).
  • Combined with input validation (regex allowlisting for format), this provides defense-in-depth.
  • The validation catches obviously invalid usernames early, while escaping ensures any special characters that pass validation are neutralized.
  • The sizeLimit option prevents denial-of-service via result flooding.
  • This pattern is essential because ldapjs does not auto-escape filter values - you must escape any user-controlled filter values yourself.

Express.js with Validation and Escaping

// SECURE - Express REST API with comprehensive security
const express = require('express');
const ldap = require('ldapjs');
const { body, query, validationResult } = require('express-validator');

const app = express();
app.use(express.json());

function escapeLDAPFilter(str) {
    return str
        .replace(/\\/g, '\\5c')
        .replace(/\*/g, '\\2a')
        .replace(/\(/g, '\\28')
        .replace(/\)/g, '\\29')
        .replace(/\x00/g, '\\00')
        .replace(/\//g, '\\2f');  // Optional: not required by RFC 4515
}

// SECURE - Input validation middleware
app.get('/api/users/search',
    query('q')
        .isLength({ min: 1, max: 50 })
        .matches(/^[a-zA-Z0-9\s]+$/)
        .withMessage('Invalid search query'),
    async (req, res) => {
        // SECURE - Check validation results
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).json({ errors: errors.array() });
        }

        const { q } = req.query;

        const client = ldap.createClient({
            url: process.env.LDAP_URL,
            timeout: 5000
        });

        try {
            await new Promise((resolve, reject) => {
                client.bind(process.env.LDAP_BIND_DN, process.env.LDAP_PASSWORD, (err) => {
                    if (err) reject(err);
                    else resolve();
                });
            });

            // SECURE - Escape search term
            const safeQuery = escapeLDAPFilter(q);
            const searchFilter = `(cn=*${safeQuery}*)`;

            const opts = {
                filter: searchFilter,
                scope: 'sub',
                attributes: ['cn', 'mail'],
                sizeLimit: 100  // SECURE - Limit results
            };

            const users = await new Promise((resolve, reject) => {
                const results = [];

                client.search(process.env.LDAP_BASE_DN, opts, (err, search) => {
                    if (err) return reject(err);

                    search.on('searchEntry', (entry) => {
                        results.push({
                            name: entry.object.cn,
                            email: entry.object.mail
                        });
                    });

                    search.on('end', () => resolve(results));
                    search.on('error', (err) => reject(err));
                });
            });

            res.json({ users, count: users.length });

        } catch (error) {
            // SECURE - Generic error message
            console.error('LDAP search error:', error);
            res.status(500).json({ error: 'Search failed' });
        } finally {
            client.unbind();
        }
    }
);

app.post('/api/login',
    body('username')
        .isLength({ min: 3, max: 64 })
        .matches(/^[a-zA-Z0-9._-]+$/)
        .withMessage('Invalid username'),
    body('password')
        .isLength({ min: 1, max: 128 })
        .withMessage('Invalid password'),
    async (req, res) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).json({ errors: errors.array() });
        }

        const { username, password } = req.body;

        try {
            const authenticated = await authenticateUser(username, password);

            if (authenticated) {
                res.json({ status: 'authenticated', username });
            } else {
                res.status(401).json({ error: 'Authentication failed' });
            }
        } catch (error) {
            console.error('Auth error:', error);
            res.status(500).json({ error: 'Authentication error' });
        }
    }
);

app.listen(3000, () => console.log('Server running on port 3000'));

Why this works:

  • Combining express-validator with manual LDAP escaping provides comprehensive protection against injection attacks.
  • Express-validator validates input format and length using declarative rules (e.g., matches(/^[a-zA-Z0-9._-]+$/) for usernames), rejecting invalid requests before LDAP operations.
  • This prevents malformed payloads from consuming resources or triggering edge cases.
  • The manual escapeLDAPFilter() function then escapes special characters in validated input, ensuring even legitimate special characters (if allowed by validation) are treated as literals.

Generic error messages prevent information disclosure - attackers shouldn't know if a username exists or why authentication failed.

Environment variables keep credentials out of source code.

Size limits (sizeLimit: 100) prevent result flooding DoS attacks.

This layered approach (validation -> escaping -> limits -> error handling) is more robust than single defenses.

TypeScript with ldapts and Validation

// SECURE - TypeScript with ldapts and comprehensive validation
import { Client, SearchOptions } from 'ldapts';

interface LDAPConfig {
    url: string;
    bindDN: string;
    bindPassword: string;
    baseDN: string;
}

class SecureLDAPService {
    private config: LDAPConfig;

    private readonly USERNAME_PATTERN = /^[a-zA-Z0-9._-]{3,64}$/;
    private readonly SEARCH_PATTERN = /^[a-zA-Z0-9\s]{1,50}$/;

    constructor(config: LDAPConfig) {
        this.config = config;
    }

    /**
     * Escape LDAP filter special characters
     */
    private escapeLDAPFilter(input: string): string {
        return input
            .replace(/\\/g, '\\5c')
            .replace(/\*/g, '\\2a')
            .replace(/\(/g, '\\28')
            .replace(/\)/g, '\\29')
            .replace(/\x00/g, '\\00')
            .replace(/\//g, '\\2f');
    }

    /**
     * Validate username format
     */
    private isValidUsername(username: string): boolean {
        return this.USERNAME_PATTERN.test(username);
    }

    /**
     * Authenticate user with LDAP
     */
    async authenticate(username: string, password: string): Promise<boolean> {
        // SECURE - Validate inputs
        if (!this.isValidUsername(username)) {
            throw new Error('Invalid username format');
        }

        if (!password || password.length > 128) {
            throw new Error('Invalid password');
        }

        const client = new Client({
            url: this.config.url,
            timeout: 5000
        });

        try {
            await client.bind(this.config.bindDN, this.config.bindPassword);

            // SECURE - Escape username
            const safeUsername = this.escapeLDAPFilter(username);
            const filter = `(&(objectClass=person)(uid=${safeUsername}))`;

            const searchOptions: SearchOptions = {
                scope: 'sub',
                filter: filter,
                attributes: ['dn'],
                sizeLimit: 1
            };

            const { searchEntries } = await client.search(
                this.config.baseDN,
                searchOptions
            );

            if (searchEntries.length === 0) {
                return false;
            }

            const userDn = searchEntries[0].dn;

            // Authenticate as user
            const userClient = new Client({ url: this.config.url });
            try {
                await userClient.bind(userDn, password);
                return true;
            } catch (err) {
                return false;
            } finally {
                await userClient.unbind();
            }

        } finally {
            await client.unbind();
        }
    }

    /**
     * Search for users
     */
    async searchUsers(searchTerm: string): Promise<Array<{ name: string; email: string }>> {
        // SECURE - Validate search term
        if (!this.SEARCH_PATTERN.test(searchTerm)) {
            throw new Error('Invalid search term');
        }

        const client = new Client({
            url: this.config.url,
            timeout: 5000
        });

        try {
            await client.bind(this.config.bindDN, this.config.bindPassword);

            // SECURE - Escape search term
            const safeTerm = this.escapeLDAPFilter(searchTerm);
            const filter = `(cn=*${safeTerm}*)`;

            const { searchEntries } = await client.search(this.config.baseDN, {
                scope: 'sub',
                filter: filter,
                attributes: ['cn', 'mail'],
                sizeLimit: 100
            });

            return searchEntries.map(entry => ({
                name: entry.cn as string,
                email: entry.mail as string
            }));

        } finally {
            await client.unbind();
        }
    }
}

export default SecureLDAPService;

Why this works:

  • Type safety and encapsulation keep LDAP calls predictable: TypeScript interfaces shape config and results, while private methods centralize validation and escaping so every request flows through the same guardrails.

Validation plus RFC 4515 escaping work together to neutralize injection - USERNAME_PATTERN and SEARCH_PATTERN reject malformed input, and escapeLDAPFilter() converts special characters to literals before they reach LDAP.

Async/await with ldapts replaces callbacks, simplifying error handling and ensuring the finally block always unbinds the client to avoid leaks.

Operational safety comes from bounded search options (sizeLimit: 100, short timeout) and scoped attribute selection, limiting data exposure.

The combination delivers a maintainable, injection-resistant TypeScript LDAP client with clear control flow and consistent cleanup.

Next.js API Route with Security

// SECURE - Next.js API route with validation and escaping
// pages/api/users/search.js
import ldap from 'ldapjs';

function escapeLDAPFilter(str) {
    return str
        .replace(/\\/g, '\\5c')
        .replace(/\*/g, '\\2a')
        .replace(/\(/g, '\\28')
        .replace(/\)/g, '\\29')
        .replace(/\x00/g, '\\00')
        .replace(/\//g, '\\2f');  // Optional: not required by RFC 4515
}

function isValidSearchQuery(query) {
    return /^[a-zA-Z0-9\s]{1,50}$/.test(query);
}

export default async function handler(req, res) {
    if (req.method !== 'GET') {
        return res.status(405).json({ error: 'Method not allowed' });
    }

    const { q } = req.query;

    // SECURE - Validate input
    if (!q || !isValidSearchQuery(q)) {
        return res.status(400).json({ error: 'Invalid query parameter' });
    }

    const client = ldap.createClient({
        url: process.env.LDAP_URL,
        timeout: 5000
    });

    try {
        await new Promise((resolve, reject) => {
            client.bind(process.env.LDAP_BIND_DN, process.env.LDAP_PASSWORD, (err) => {
                if (err) reject(err);
                else resolve();
            });
        });

        // SECURE - Escape query
        const safeQuery = escapeLDAPFilter(q);
        const filter = `(cn=*${safeQuery}*)`;

        const results = await new Promise((resolve, reject) => {
            const users = [];
            let count = 0;

            client.search(process.env.LDAP_BASE_DN, {
                filter: filter,
                scope: 'sub',
                attributes: ['cn', 'mail'],
                sizeLimit: 100
            }, (err, search) => {
                if (err) return reject(err);

                search.on('searchEntry', (entry) => {
                    if (count < 100) {  // Additional safety check
                        users.push({
                            name: entry.object.cn,
                            email: entry.object.mail
                        });
                        count++;
                    }
                });

                search.on('end', () => resolve(users));
                search.on('error', (err) => reject(err));
            });
        });

        res.status(200).json({ results, count: results.length });

    } catch (error) {
        console.error('LDAP search error:', error);
        res.status(500).json({ error: 'Search failed' });
    } finally {
        client.unbind();
    }
}

Why this works:

  • Next.js API routes run in a serverless environment where LDAP credentials live in environment variables and never reach client code.
  • The handler gates on the HTTP method (GET only) and runs isValidSearchQuery() to keep input to short, alphanumeric-plus-space terms.
  • That validation blocks malformed payloads and overly long queries, while escapeLDAPFilter() applies RFC 4515 escaping so any special characters are treated as literals instead of filter operators.

Async/await wrappers around ldapjs convert callbacks to promises, making error handling consistent and letting the finally block always close connections to avoid leaks.

Double safety limits - the sizeLimit option plus a manual count check - stop result-flooding attempts.

Generic client errors avoid leaking directory structure, with detailed errors staying in server logs. Together these controls make the endpoint resistant to LDAP injection and resource-exhaustion attacks in a serverless setting where you cannot rely on long-lived connection pools.

Verification

After implementing the recommended secure patterns, verify the fix through multiple approaches:

  • Manual testing: Submit malicious payloads relevant to this vulnerability and confirm they're handled safely without executing unintended operations
  • Code review: Confirm all instances use the secure pattern (parameterized queries, safe APIs, proper encoding) with no string concatenation or unsafe operations
  • Static analysis: Use security scanners to verify no new vulnerabilities exist and the original finding is resolved
  • Regression testing: Ensure legitimate user inputs and application workflows continue to function correctly
  • Edge case validation: Test with special characters, boundary conditions, and unusual inputs to verify proper handling
  • Framework verification: If using a framework or library, confirm the recommended APIs are used correctly according to documentation
  • Authentication/session testing: Verify security controls remain effective and cannot be bypassed (if applicable to the vulnerability type)
  • Rescan: Run the security scanner again to confirm the finding is resolved and no new issues were introduced

Additional Resources