diff --git a/README.md b/README.md
index 9e9e2f3..dad291d 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,18 @@
A Spring Boot application containing intentionally vulnerable code patterns for testing application security tools. Each pattern exercises a distinct data-flow complexity, making this a practical benchmark for taint analysis engines.
+## Vulnerability Patterns
+
+Intentionally vulnerable patterns, grouped by category:
+
+### XSS Complexity
+
+1. Direct user input return
+2. Local variable assignment
+3. Inter-procedural flow
+4. Constructor chains and field sensitivity
+5. Builder pattern and virtual method calls
+
## Scanning with OpenTaint
Detect vulnerabilities using [OpenTaint](https://opentaint.org/):
diff --git a/build.gradle.kts b/build.gradle.kts
index 5357967..46960b5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,7 +4,7 @@ plugins {
id("io.spring.dependency-management") version "1.1.6"
}
-group = "org.example"
+group = "org.seqra"
version = "1.0-SNAPSHOT"
java {
@@ -18,7 +18,6 @@ repositories {
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
- implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
diff --git a/src/main/java/org/seqra/complexity/DefaultFormatter.java b/src/main/java/org/seqra/complexity/DefaultFormatter.java
new file mode 100644
index 0000000..548a7d9
--- /dev/null
+++ b/src/main/java/org/seqra/complexity/DefaultFormatter.java
@@ -0,0 +1,8 @@
+package org.seqra.complexity;
+
+public class DefaultFormatter implements IFormatter {
+ @Override
+ public String format(String value) {
+ return value;
+ }
+}
diff --git a/src/main/java/org/seqra/complexity/EscapeFormatter.java b/src/main/java/org/seqra/complexity/EscapeFormatter.java
new file mode 100644
index 0000000..2ec1fca
--- /dev/null
+++ b/src/main/java/org/seqra/complexity/EscapeFormatter.java
@@ -0,0 +1,10 @@
+package org.seqra.complexity;
+
+import org.springframework.web.util.HtmlUtils;
+
+public class EscapeFormatter implements IFormatter {
+ @Override
+ public String format(String value) {
+ return HtmlUtils.htmlEscape(value);
+ }
+}
diff --git a/src/main/java/org/seqra/complexity/HtmlPageBuilder.java b/src/main/java/org/seqra/complexity/HtmlPageBuilder.java
new file mode 100644
index 0000000..1f9b2b3
--- /dev/null
+++ b/src/main/java/org/seqra/complexity/HtmlPageBuilder.java
@@ -0,0 +1,27 @@
+package org.seqra.complexity;
+
+import org.springframework.web.util.HtmlUtils;
+
+public class HtmlPageBuilder {
+
+ private String message = "";
+
+ public HtmlPageBuilder message(String message) {
+ this.message = message;
+ return this;
+ }
+
+ public HtmlPageBuilder escape() {
+ this.message = HtmlUtils.htmlEscape(this.message);
+ return this;
+ }
+
+ public HtmlPageBuilder format(IFormatter formatter) {
+ this.message = formatter.format(this.message);
+ return this;
+ }
+
+ public String buildPage() {
+ return "
Profile Message: " + message + "
";
+ }
+}
diff --git a/src/main/java/org/seqra/complexity/IFormatter.java b/src/main/java/org/seqra/complexity/IFormatter.java
new file mode 100644
index 0000000..dbbc92d
--- /dev/null
+++ b/src/main/java/org/seqra/complexity/IFormatter.java
@@ -0,0 +1,5 @@
+package org.seqra.complexity;
+
+public interface IFormatter {
+ String format(String value);
+}
diff --git a/src/main/java/org/seqra/complexity/Profile.java b/src/main/java/org/seqra/complexity/Profile.java
new file mode 100644
index 0000000..7ab5827
--- /dev/null
+++ b/src/main/java/org/seqra/complexity/Profile.java
@@ -0,0 +1,76 @@
+package org.seqra.complexity;
+
+import org.springframework.web.util.HtmlUtils;
+
+public class Profile {
+ // User profile data structure
+ public static class UserProfile {
+ public UserSettings settings;
+
+ public UserProfile(UserSettings settings) {
+ this.settings = settings;
+ }
+
+ public UserProfile(String text) {
+ this.settings = new UserSettings(text);
+ }
+ }
+
+ public static class UserSettings {
+ public NotificationConfig config;
+
+ public UserSettings(NotificationConfig config) {
+ this.config = config;
+ }
+
+ public UserSettings(String text) {
+ this.config = new NotificationConfig(text);
+ }
+ }
+
+ public static class NotificationConfig {
+ public MessageTemplate template;
+
+ public NotificationConfig(MessageTemplate template) {
+ this.template = template;
+ }
+
+ public NotificationConfig(String text) {
+ this.template = new MessageTemplate(text);
+ }
+ }
+
+ public static class MessageTemplate {
+ public MessageBody body;
+
+ public MessageTemplate(MessageBody body) {
+ this.body = body;
+ }
+
+ public MessageTemplate(String text) {
+ this.body = new MessageBody("" + text + "");
+ }
+ }
+
+ public static class MessageBody {
+ public MessageContent content;
+
+ public MessageBody(MessageContent content) {
+ this.content = content;
+ }
+
+ public MessageBody(String text) {
+ this.content = new MessageContent("" + text + "");
+ }
+ }
+
+ public static class MessageContent {
+ public String text;
+ public String secureText;
+
+ public MessageContent(String text) {
+ this.text = "Notification: " + text + "
";
+ this.secureText = "Notification: " + HtmlUtils.htmlEscape(text) + "
";
+ }
+ }
+}
diff --git a/src/main/java/org/seqra/complexity/UserProfileController.java b/src/main/java/org/seqra/complexity/UserProfileController.java
new file mode 100644
index 0000000..105e1f3
--- /dev/null
+++ b/src/main/java/org/seqra/complexity/UserProfileController.java
@@ -0,0 +1,171 @@
+package org.seqra.complexity;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.util.HtmlUtils;
+
+@Controller
+public class UserProfileController {
+
+ // Display user profile with custom message
+ @GetMapping("/profile/display")
+ @ResponseBody
+ public String displayUserProfile(
+ @RequestParam(defaultValue = "Welcome") String message) {
+ // Direct output without escaping
+ return "Profile Message: " + message + "
";
+ }
+
+ // Display user profile with escaped message
+ @GetMapping("/profile/secureDisplay")
+ @ResponseBody
+ public String displaySecureUserProfile(
+ @RequestParam(defaultValue = "Welcome") String message) {
+ // Properly escaped output
+ return "Profile Message: " +
+ HtmlUtils.htmlEscape(message) + "
";
+ }
+
+ // Display user status with local variable assignment
+ @GetMapping("/profile/status")
+ @ResponseBody
+ public String displayUserStatus(
+ @RequestParam(defaultValue = "Active") String message) {
+ // Assign to local variable
+ String htmlContent = "User Status: " +
+ message + "
";
+ return htmlContent;
+ }
+
+ // Display escaped user status with local variable assignment
+ @GetMapping("/profile/secureStatus")
+ @ResponseBody
+ public String displaySecureUserStatus(
+ @RequestParam(defaultValue = "Active") String message) {
+ // Assign to local variable
+ String htmlContent = "User Status: " +
+ HtmlUtils.htmlEscape(message) + "
";
+ return htmlContent;
+ }
+
+ // Generate user dashboard with escaped greeting
+ @GetMapping("/dashboard/greeting")
+ @ResponseBody
+ public String generateDashboard(
+ @RequestParam(defaultValue = "Welcome") String greeting) {
+ String htmlContent = buildDashboardContent(greeting);
+ return htmlContent;
+ }
+
+ private static String buildDashboardContent(String greeting) {
+ // Generate dashboard HTML content
+ return "Dashboard: " + greeting + "
";
+ }
+
+ // Generate user dashboard with custom greeting
+ @GetMapping("/dashboard/secureGreeting")
+ @ResponseBody
+ public String generateSecureDashboard(
+ @RequestParam(defaultValue = "Welcome") String greeting) {
+ String htmlContent = buildSecureDashboardContent(greeting);
+ return htmlContent;
+ }
+
+ private static String buildSecureDashboardContent(String greeting) {
+ // Generate dashboard HTML content with escaped greeting
+ return "Dashboard: " +
+ HtmlUtils.htmlEscape(greeting) + "
";
+ }
+
+ // Generate message template
+ @GetMapping("/notifications/template")
+ @ResponseBody
+ public String generateTemplate(
+ @RequestParam(defaultValue = "New Message") String content) {
+ Profile.MessageTemplate template = new Profile.MessageTemplate(content);
+ // Return nested content
+ return template.body.content.text;
+ }
+
+ // Generate message template
+ @GetMapping("/notifications/secureTemplate")
+ @ResponseBody
+ public String generateSecureTemplate(
+ @RequestParam(defaultValue = "New Message") String content) {
+ Profile.MessageTemplate template = new Profile.MessageTemplate(content);
+ // Return nested escaped content
+ return template.body.content.secureText;
+ }
+
+ // Generate user notification with complex data structure
+ @GetMapping("/notifications/generate")
+ @ResponseBody
+ public String generateNotification(
+ @RequestParam(defaultValue = "New Message") String content) {
+ // Create user profile with nested message structure using constructors
+ Profile.UserProfile profile = new Profile.UserProfile(content);
+
+ // Return nested content
+ return profile.settings.config.template.body.content.text;
+ }
+
+ // Generate user notification with complex data structure
+ @GetMapping("/notifications/secureGenerate")
+ @ResponseBody
+ public String generateSecureNotification(
+ @RequestParam(defaultValue = "New Message") String content) {
+ // Create user profile with nested message structure using constructors
+ Profile.UserProfile profile = new Profile.UserProfile(content);
+
+ // Return nested content
+ return profile.settings.config.template.body.content.secureText;
+ }
+
+ // Display custom message
+ @GetMapping("/message/display")
+ @ResponseBody
+ public String displayMessage(
+ @RequestParam(defaultValue = "Welcome") String message) {
+ // Construct a page using a chain of builders
+ String page = new HtmlPageBuilder().message(message).buildPage();
+
+ return page;
+ }
+
+ // Display custom message
+ @GetMapping("/message/secureDisplay")
+ @ResponseBody
+ public String displaySecureMessage(
+ @RequestParam(defaultValue = "Welcome") String message) {
+ // Construct a page using a chain of builders
+ String page = new HtmlPageBuilder().message(message).escape().buildPage();
+
+ return page;
+ }
+
+ // Display formatted message
+ @GetMapping("/message/format")
+ @ResponseBody
+ public String formatMessage(
+ @RequestParam(defaultValue = "Welcome") String message) {
+ // Construct a page using a formatter as a parameter for a chain of builders
+ String page = new HtmlPageBuilder().message(message)
+ .format(new DefaultFormatter()).buildPage();
+
+ return page;
+ }
+
+ // Display escaped message
+ @GetMapping("/message/escape")
+ @ResponseBody
+ public String escapeMessage(
+ @RequestParam(defaultValue = "Welcome") String message) {
+ // Construct a page using a formatter as a parameter for a chain of builders
+ String page = new HtmlPageBuilder().message(message)
+ .format(new EscapeFormatter()).buildPage();
+
+ return page;
+ }
+}