CWE-91: XML Injection - Java
Overview
XML Injection in Java applications occurs when untrusted user input is used to construct XML documents or queries without proper validation or escaping. Attackers can manipulate XML structure by injecting special characters like <, >, &, ', and ", leading to data corruption, authentication bypass, or information disclosure.
Primary Defence: Use DOM API (DocumentBuilder, Element.setAttribute()) or JDOM2 with proper element creation methods instead of string concatenation, validate and escape user input with StringEscapeUtils.escapeXml() from Apache Commons Text or implement custom escaping for XML special characters, disable external entity processing (DocumentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)) to prevent XXE attacks, and use parameterized XPath queries to prevent XML/XPath injection.
Common Java XML Vulnerability Scenarios:
- Building XML documents with string concatenation
- Using user input directly in XML elements or attributes
- Manual XML parsing without escaping
- XPath query injection
- SOAP/REST XML payloads with unsanitized data
Java XML APIs:
- org.w3c.dom: W3C DOM API (javax.xml.parsers)
- javax.xml.stream (StAX): Streaming API for XML
- org.jdom2: JDOM library
- Jackson XML: Data binding for XML
- JAX-B: Java Architecture for XML Binding
- org.apache.commons.text.StringEscapeUtils: XML escaping utility
XML Special Characters Requiring Escaping:
<→<>→>&→&'→'"→"
Common Vulnerable Patterns
String Concatenation
// VULNERABLE - Direct string concatenation
public class VulnerableXmlBuilder {
public String createUserXml(String username, String email) {
// VULNERABLE - User input directly in XML string
String xml = "<?xml version=\"1.0\"?>\n" +
"<user>\n" +
" <username>" + username + "</username>\n" +
" <email>" + email + "</email>\n" +
"</user>";
return xml;
}
}
// Attack: username = "</username><admin>true</admin><username>"
// Result: <username></username><admin>true</admin><username></username>
// Creates unintended <admin> element
Why this is vulnerable:
- No escaping of XML special characters
- Allows injection of new elements
- Can modify XML structure
- Bypasses validation
Spring Boot REST API
// VULNERABLE - Spring Boot endpoint returning XML
package com.example.api;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.*;
@RestController
@RequestMapping("/api")
public class VulnerableUserController {
@GetMapping(value = "/user", produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<String> getUser(
@RequestParam String username,
@RequestParam String email) {
// VULNERABLE - String concatenation in XML
String xmlResponse = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<response>\n" +
" <user>\n" +
" <name>" + username + "</name>\n" +
" <email>" + email + "</email>\n" +
" </user>\n" +
"</response>";
return ResponseEntity.ok(xmlResponse);
}
}
// Attack: username = "<admin>true</admin>"
// Response contains: <name><admin>true</admin></name>
Why this is vulnerable:
- Spring doesn't auto-escape manual XML strings
- Request parameters directly in XML
- No validation or sanitization
- Information disclosure possible
SOAP Request Construction
// VULNERABLE - Building SOAP XML manually
import java.net.http.*;
import java.net.URI;
public class VulnerableSoapClient {
public String callSoapService(String userId, String action) throws Exception {
// VULNERABLE - User input in SOAP envelope
String soapEnvelope = "<?xml version=\"1.0\"?>\n" +
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" +
" <soap:Body>\n" +
" <GetUserData>\n" +
" <UserId>" + userId + "</UserId>\n" +
" <Action>" + action + "</Action>\n" +
" </GetUserData>\n" +
" </soap:Body>\n" +
"</soap:Envelope>";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/soap"))
.header("Content-Type", "text/xml")
.POST(HttpRequest.BodyPublishers.ofString(soapEnvelope))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
return response.body();
}
}
// Attack: userId = "</UserId><Role>admin</Role><UserId>"
// Injects admin role into SOAP request
Why this is vulnerable:
- SOAP envelope built with concatenation
- Allows element injection
- Can escalate privileges
- Modify request structure
XML Configuration Files
// VULNERABLE - Writing XML config with user data
import java.io.*;
public class VulnerableConfigWriter {
public void saveUserSettings(String username, String theme, String language)
throws IOException {
// VULNERABLE - User input in XML config
String configXml = "<?xml version=\"1.0\"?>\n" +
"<config>\n" +
" <user>" + username + "</user>\n" +
" <preferences>\n" +
" <theme>" + theme + "</theme>\n" +
" <language>" + language + "</language>\n" +
" </preferences>\n" +
"</config>";
try (FileWriter writer = new FileWriter("config.xml")) {
writer.write(configXml);
}
}
}
// Attack: theme = "</theme><admin_access>true</admin_access><theme>"
// Modifies configuration structure
Why this is vulnerable:
- Configuration files parsed by XML parser
- Persistent injection
- Can modify application behavior
- Privilege escalation
DOM Builder with String Input
// VULNERABLE - DOM parsing of concatenated string
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.*;
public class VulnerableDomBuilder {
public Document createXmlResponse(String data) throws Exception {
// VULNERABLE - Building XML string manually
String xmlStr = "<response>\n" +
" <status>success</status>\n" +
" <data>" + data + "</data>\n" +
"</response>";
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
// Parse the vulnerable XML
return builder.parse(new ByteArrayInputStream(xmlStr.getBytes()));
}
}
// Attack: data = "</data><malicious>payload</malicious><data>"
// Injects malicious elements
Why this is vulnerable:
- DOM doesn't escape string concatenation
- Parser accepts malformed structure
- Injection before parsing
- No validation
XPath Query Injection
// VULNERABLE - User input in XPath query
import org.w3c.dom.*;
import javax.xml.xpath.*;
import javax.xml.parsers.*;
public class VulnerableXPathQuery {
public NodeList findUserByName(Document doc, String username) throws Exception {
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
// VULNERABLE - XPath injection
String xpathExpr = "//user[name='" + username + "']";
XPathExpression expression = xpath.compile(xpathExpr);
return (NodeList) expression.evaluate(doc, XPathConstants.NODESET);
}
}
// Attack: username = "' or '1'='1"
// XPath: //user[name='' or '1'='1']
// Returns all users
Why this is vulnerable:
- XPath query built with concatenation
- Boolean-based injection
- Bypasses authentication checks
- Information disclosure
XML Attribute Injection
// VULNERABLE - User input in XML attributes
public class VulnerableAttributeBuilder {
public String createElement(String name, String value, String attrValue) {
// VULNERABLE - Attribute injection
String xml = "<element name=\"" + name + "\" " +
"value=\"" + value + "\" " +
"custom=\"" + attrValue + "\"/>";
return xml;
}
}
// Attack: attrValue = "test\" malicious=\"true"
// Result: <element name="..." value="..." custom="test" malicious="true"/>
// Injects additional attributes
Why this is vulnerable:
- Attribute quotes can be escaped
- Allows additional attribute injection
- Modifies element properties
- Can bypass security checks
JAX-RS XML Response
// VULNERABLE - JAX-RS endpoint with manual XML
import javax.ws.rs.*;
import javax.ws.rs.core.*;
@Path("/api")
public class VulnerableXmlResource {
@GET
@Path("/users/{username}")
@Produces(MediaType.APPLICATION_XML)
public Response getUserInfo(@PathParam("username") String username) {
try {
// VULNERABLE - Path parameter in XML
String xml = "<?xml version=\"1.0\"?>\n" +
"<user>\n" +
" <username>" + username + "</username>\n" +
" <status>active</status>\n" +
"</user>";
return Response.ok(xml).build();
} catch (Exception e) {
// VULNERABLE - Error details exposed
return Response.serverError()
.entity("<error>" + e.getMessage() + "</error>")
.build();
}
}
}
Why this is vulnerable:
- URL path parameter directly in XML
- No validation or escaping
- Detailed error messages
- REST API exposes injection point
Secure Patterns
DOM API with Proper Methods
// SECURE - Using W3C DOM API (recommended)
import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import java.io.*;
import java.util.regex.*;
public class SecureXmlBuilder {
private static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9._-]{1,100}$");
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
public String createUserXml(String username, String email) throws Exception {
// SECURE - Validate inputs
if (!USERNAME_PATTERN.matcher(username).matches()) {
throw new IllegalArgumentException("Invalid username");
}
if (!EMAIL_PATTERN.matcher(email).matches()) {
throw new IllegalArgumentException("Invalid email");
}
// SECURE - Use DOM API
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
Element root = doc.createElement("user");
doc.appendChild(root);
Element usernameElem = doc.createElement("username");
usernameElem.setTextContent(username); // Automatically escaped
root.appendChild(usernameElem);
Element emailElem = doc.createElement("email");
emailElem.setTextContent(email); // Automatically escaped
root.appendChild(emailElem);
// Convert to string
return documentToString(doc);
}
private String documentToString(Document doc) throws Exception {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(doc), new StreamResult(writer));
return writer.toString();
}
}
// Example usage:
// createUserXml("<script>alert('xss')</script>", "test@example.com")
// Result: <username><script>alert('xss')</script></username>
// Special characters properly escaped
Why this works: W3C DOM API (Document.createElement(), Element.setTextContent()) automatically escapes XML special characters (<, >, &, ', ") when setting text content, preventing attackers from injecting closing tags like </username><admin>true</admin><username>. The setTextContent() method treats the parameter as character data, not markup - so even if username contains <admin>true</admin>, it becomes <admin>true</admin> in the serialized XML.
Regex validation provides defense-in-depth: the username pattern (^[a-zA-Z0-9._-]{1,100}$) blocks XML metacharacters before they reach the API, and email validation prevents addresses like admin@example.com</email><role>admin</role><email>. Transformer with DOMSource converts the in-memory DOM tree to XML text safely, producing syntactically valid output with proper escaping. OutputKeys.INDENT formats the XML for readability without introducing injection vectors.
This pattern is immune to injection because the DOM API maintains a tree structure internally and serializes it safely, never concatenating raw strings. Trade-off: DOM loads the entire document into memory, making it unsuitable for very large XML (use StAX for streaming).
StAX for Streaming XML
// SECURE - Using StAX (Streaming API for XML)
import javax.xml.stream.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
public class SecureStaxBuilder {
private static final Pattern KEY_PATTERN = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_-]*$");
public String createXmlWithStax(Map<String, String> data) throws Exception {
// SECURE - Validate input
for (Map.Entry<String, String> entry : data.entrySet()) {
if (!KEY_PATTERN.matcher(entry.getKey()).matches()) {
throw new IllegalArgumentException("Invalid XML element name: " + entry.getKey());
}
if (entry.getValue() != null && entry.getValue().length() > 1000) {
throw new IllegalArgumentException("Value too long");
}
}
StringWriter stringWriter = new StringWriter();
XMLStreamWriter writer = XMLOutputFactory.newInstance()
.createXMLStreamWriter(stringWriter);
// SECURE - StAX API handles escaping
writer.writeStartDocument("UTF-8", "1.0");
writer.writeStartElement("response");
writer.writeStartElement("status");
writer.writeCharacters("success"); // Auto-escaped
writer.writeEndElement();
writer.writeStartElement("data");
for (Map.Entry<String, String> entry : data.entrySet()) {
writer.writeStartElement(entry.getKey());
writer.writeCharacters(entry.getValue()); // Auto-escaped
writer.writeEndElement();
}
writer.writeEndElement(); // data
writer.writeEndElement(); // response
writer.writeEndDocument();
writer.close();
return stringWriter.toString();
}
}
Why this works: StAX (XMLStreamWriter) provides low-level streaming control with automatic escaping - methods like writeCharacters() and writeAttribute() escape XML entities (< → <, & → &) without requiring manual StringEscapeUtils calls. The streaming API writes XML incrementally to a StringWriter or file, making it memory-efficient for large documents (gigabytes) compared to DOM's in-memory tree.
Element name validation (^[a-zA-Z_][a-zA-Z0-9_-]*$) prevents injection via malformed element names like user><admin>true</admin><user - XML parsers reject invalid element names, but validating upfront provides fail-fast behavior. Length limits (1000 chars) prevent DoS via extremely long values that could exhaust memory or cause parser hangs. writeStartElement / writeEndElement pairing ensures proper nesting - forgetting writeEndElement causes XMLStreamWriter to throw XMLStreamException, preventing malformed XML.
writeCharacters() escapes content but not CDATA sections - use writeCData() for literal content, though avoid CDATA with user input (attackers can inject ]]> to break out). This pattern is ideal for generating large XML exports (RSS feeds, data dumps) where DOM's memory overhead is prohibitive.
Apache Commons Text Escaping
// SECURE - Using Apache Commons Text for escaping
import org.apache.commons.text.StringEscapeUtils;
import java.util.regex.*;
public class SecureXmlEscaper {
private static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9._-]{1,100}$");
public String createUserXmlWithEscaping(String username, String email) {
// SECURE - Validate inputs
if (!USERNAME_PATTERN.matcher(username).matches()) {
throw new IllegalArgumentException("Invalid username");
}
// SECURE - Use StringEscapeUtils to escape XML
String safeUsername = StringEscapeUtils.escapeXml11(username);
String safeEmail = StringEscapeUtils.escapeXml11(email);
StringBuilder xml = new StringBuilder();
xml.append("<?xml version=\"1.0\"?>\n");
xml.append("<user>\n");
xml.append(" <username>").append(safeUsername).append("</username>\n");
xml.append(" <email>").append(safeEmail).append("</email>\n");
xml.append("</user>");
return xml.toString();
}
}
// Maven dependency:
// <dependency>
// <groupId>org.apache.commons</groupId>
// <artifactId>commons-text</artifactId>
// <version>1.10.0</version>
// </dependency>
Why this works: StringEscapeUtils.escapeXml11() is a battle-tested library method that escapes the five XML special characters (< → <, > → >, & → &, ' → ', " → ") plus control characters (0x00-0x1F except tab/newline/carriage return), making it safe to embed user input in manually constructed XML strings. This is useful when DOM or StAX are impractical (e.g., integrating with legacy code that expects XML strings, or templating scenarios).
Pre-validation (^[a-zA-Z0-9._-]{1,100}$) provides defense-in-depth - even if escapeXml11() has edge cases or encoding issues, the allowlist blocks malicious input. XML 1.1 escaping (escapeXml11) handles Unicode surrogates and control characters that XML 1.0 doesn't support - use escapeXml10() for XML 1.0 documents. However, this pattern is less preferred than DOM or StAX because manual string concatenation is error-prone - developers might forget to escape a variable, or escape incorrectly.
Apache Commons Text (successor to Commons Lang3's StringEscapeUtils) is widely used and maintained. Note: this method only escapes content, not attribute values in all contexts - for complex scenarios, DOM is safer. Use this pattern only when APIs like DOM are unavailable and you understand the escaping rules.
Spring Boot with Jackson XML
// SECURE - Spring Boot with Jackson XML data binding
package com.example.api;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.*;
import com.fasterxml.jackson.dataformat.xml.annotation.*;
import java.util.regex.*;
@RestController
@RequestMapping("/api")
public class SecureUserController {
private static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9._-]{3,64}$");
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
@GetMapping(value = "/user", produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<?> getUser(
@RequestParam String username,
@RequestParam String email) {
// SECURE - Validate inputs
if (!USERNAME_PATTERN.matcher(username).matches()) {
return ResponseEntity.badRequest()
.body(new ErrorResponse("Invalid username"));
}
if (!EMAIL_PATTERN.matcher(email).matches()) {
return ResponseEntity.badRequest()
.body(new ErrorResponse("Invalid email"));
}
// SECURE - Jackson handles XML escaping
UserResponse response = new UserResponse(username, email);
return ResponseEntity.ok(response);
}
@JacksonXmlRootElement(localName = "response")
static class UserResponse {
@JacksonXmlProperty
private String name;
@JacksonXmlProperty
private String email;
public UserResponse(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() { return name; }
public String getEmail() { return email; }
}
@JacksonXmlRootElement(localName = "error")
static class ErrorResponse {
@JacksonXmlProperty
private String message;
public ErrorResponse(String message) {
this.message = message;
}
public String getMessage() { return message; }
}
}
Why this works: Jackson XML (@JacksonXmlRootElement, @JacksonXmlProperty) uses reflection and annotations to serialize Java objects to XML, automatically escaping property values during marshalling - even if name contains <script>alert('xss')</script>, it becomes <script>alert('xss')</script> in the XML output. Regex validation (^[a-zA-Z0-9._-]{3,64}$ for usernames, email pattern) blocks XML metacharacters before they reach the serializer, preventing injection attempts like username=</name><admin>true</admin><name>.
Type safety ensures only declared properties (name, email) appear in the XML - attackers can't inject arbitrary elements like <admin>true</admin> because the UserResponse class doesn't have an admin field. @JacksonXmlRootElement controls the root element name, making the mapping explicit and auditable. Spring's @Produces(MediaType.APPLICATION_XML_VALUE) sets the correct Content-Type: application/xml header, ensuring clients parse the response correctly.
Generic error messages ("Invalid username") prevent information disclosure - attackers don't learn whether rejection was due to regex mismatch, length limits, or null input. This pattern is ideal for Spring Boot REST APIs returning XML responses to legacy clients or RSS readers, with cleaner code than manual DOM construction.
JAX-B for Object Mapping
// SECURE - JAX-B for XML marshalling
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import java.io.*;
import java.util.regex.*;
@XmlRootElement(name = "user")
@XmlAccessorType(XmlAccessType.FIELD)
class User {
@XmlElement
private String username;
@XmlElement
private String email;
@XmlElement
private String bio;
// Constructors, getters, setters
public User() {}
public User(String username, String email, String bio) {
this.username = username;
this.email = email;
this.bio = bio;
}
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getBio() { return bio; }
public void setBio(String bio) { this.bio = bio; }
}
public class SecureJaxbMarshaller {
private static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9._-]{1,100}$");
public String marshalUser(String username, String email, String bio) throws Exception {
// SECURE - Validate inputs
if (!USERNAME_PATTERN.matcher(username).matches()) {
throw new IllegalArgumentException("Invalid username");
}
// SECURE - JAX-B handles XML escaping
User user = new User(username, email, bio);
JAXBContext context = JAXBContext.newInstance(User.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter writer = new StringWriter();
marshaller.marshal(user, writer);
return writer.toString();
}
}
Why this works: JAX-B (@XmlRootElement, @XmlElement, Marshaller) uses standard Java annotations to map Java classes to XML, automatically escaping property values during marshalling - even if bio contains <script>alert('xss')</script>, it becomes <script>alert('xss')</script> in the XML output. Type safety ensures only declared fields (username, email, bio) appear in the XML - attackers can't inject arbitrary elements like <admin>true</admin> because the User class doesn't have an admin field. @XmlAccessorType(XmlAccessType.FIELD) controls which fields are serialized, making the mapping explicit.
Pre-validation (^[a-zA-Z0-9._-]{1,100}$) provides defense-in-depth, though JAX-B escaping is sufficient. Marshaller.JAXB_FORMATTED_OUTPUT formats the XML for readability without introducing injection vectors. JAXBContext.newInstance(User.class) creates a thread-safe context that can be cached for performance.
This approach is ideal for JAX-WS SOAP services and REST APIs with complex data models (nested objects, collections, enums) where manual DOM construction becomes verbose. Trade-off: JAX-B requires no-arg constructors and mutable fields (or @XmlAccessorType), limiting use with immutable types. Note: JAX-B was removed from Java SE 11+ - add jakarta.xml.bind-api and jaxb-runtime dependencies for modern Java.
XPath with Parameterization
// SECURE - XPath with safe practices
import org.w3c.dom.*;
import javax.xml.xpath.*;
import java.util.regex.*;
public class SecureXPathQuery {
private static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9._-]{1,50}$");
public Node findUserByNameSecure(Document doc, String username) throws Exception {
// SECURE - Validate input
if (!USERNAME_PATTERN.matcher(username).matches()) {
throw new IllegalArgumentException("Invalid username format");
}
// SECURE - Iterate and compare (safest approach)
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
XPathExpression expression = xpath.compile("//user");
NodeList users = (NodeList) expression.evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < users.getLength(); i++) {
Node user = users.item(i);
NodeList children = user.getChildNodes();
for (int j = 0; j < children.getLength(); j++) {
Node child = children.item(j);
if ("name".equals(child.getNodeName()) &&
username.equals(child.getTextContent())) {
return user;
}
}
}
return null;
}
}
Why this works: XPath injection is prevented by avoiding string concatenation in XPath expressions - instead of xpath.compile("//user[name='" + username + "']") (which allows injection like username="alice' or '1'='1'"), this pattern uses compile("//user") + iteration + string comparison (username.equals(child.getTextContent())). Regex validation (^[a-zA-Z0-9._-]{1,50}$) blocks XPath metacharacters (', ", [, ], *, /, @) before they reach the query, preventing expressions like //user[name='' or '1'='1'] that return all users.
Exact string comparison (equals()) after fetching nodes ensures no wildcards or boolean logic - even if an attacker bypasses validation, the comparison fails unless there's an exact match. Static XPath ("//user", "name") contains no user input, avoiding injection. This "iterate and compare" approach is the safest XPath pattern because it eliminates the attack surface entirely.
Alternative: Java's XPath APIs don't support true parameterized queries like SQL's PreparedStatement - the safest approach remains avoiding user input in XPath strings. This pattern is less efficient (O(n) iteration) but maximally secure for small XML documents. For large documents, consider indexed lookups or XPath with EXSLT extensions, though iterate-and-compare is simplest.
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