Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions checkers/javascript/dangerous_xss_unsanitized_input.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
function test_dangerous_xss_unsanitized_input() {
// These should be flagged for XSS vulnerabilities
let user_input = getUserInput();

// <expect-error>
document.body.innerHTML = user_input;

// <expect-error>
document.write(user_input);

// <expect-error>
document.body.innerHTML = "<div>" + user_input + "</div>";

// These are safe and should not be flagged
let safe_input = "Safe content";
document.body.innerHTML = safe_input; // No user input involved

// This should be flagged even inside a function definition
function dangerousFunction() {
// <expect-error>
document.body.innerHTML = getUserInput();
}

try {
// This should not be flagged because it's inside a catch block
document.body.innerHTML = user_input;
} catch (err) {
console.error(err);
}
}

function getUserInput() {
return "<script>alert('XSS!')</script>";
}

const htmlContent = `<div>${userInput}</div>`;
// <expect-error>
document.getElementById("output").innerHTML = htmlContent;

function test_dangerous_dom_operations() {
const userInput = getUserInput();
const element = document.getElementById("content");

// These should be flagged

// <expect-error>
element.innerHTML = userInput;

// <expect-error>
element.innerHTML = "<div>" + userInput + "</div>";

// <expect-error>
element.insertAdjacentHTML("beforeend", `${userInput}`);

// These are safe and should not be flagged

// Safe because `sanitizeHTML()` sanitizes `userInput` before insertion
document.getElementById("output").innerHTML = sanitizeHTML(userInput);

// Safe because there's no user input involved
document.getElementById("output").innerHTML = "<p>Safe Content</p>";

// Safe - using textContent
element.textContent = userInput;

// Safe - using createElement
const div = document.createElement("div");
div.textContent = userInput;
element.appendChild(div);

// Safe - using static HTML
element.innerHTML = "<div>Static content</div>";
}

function test_edge_cases() {
const element = document.querySelector(".content");

// Should not flag property access
const currentHTML = element.innerHTML;

// Should not flag non-HTML string concatenation
const message = "Hello, " + username;

// Should not flag commented code
// element.innerHTML = userInput;
}

// Helper function to simulate user input
function getUserInput() {
return "user provided content";
}
245 changes: 245 additions & 0 deletions checkers/javascript/dangerous_xss_unsanitized_input.yml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests are failing with this message:

invalid rule 'dangerous_xss_unsanitized_input.yml': invalid node type 'argument_list' at line 15 column 0

I'd recommend running globstar test locally to see if all the checkers are running as expected and the test cases are raising errors as expected.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2025-03-11 233044
Hi sanket,
I've made the required changes and tested it locally and it seems to working well. I've also added some more checkers to detect some more potentially dangerous DOM manipulations.
please have a look at the updated code.

Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
language: javascript
name: dangerous_xss_unsanitized_input
message: "Unsanitized DOM manipulation detected. This could lead to XSS vulnerabilities."
category: security
severity: critical
pattern: |
[

(assignment_expression
left: (member_expression
object: (member_expression
object: (identifier) @doc
(#match? @doc "^(document|element|elem|node|div|span|container|wrapper|section|component)$")
property: (property_identifier) @prop1
(#match? @prop1 "^(body|innerHTML|outerHTML)$"))
property: (property_identifier) @prop2
(#eq? @prop2 "innerHTML"))
right: (identifier) @id
(#match? @id "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$")) @dangerous_xss_unsanitized_input


(call_expression
function: (member_expression
object: (identifier) @doc
property: (property_identifier) @write)
arguments: (arguments
(identifier) @id)
(#eq? @doc "document")
(#eq? @write "write")
(#match? @id "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$")) @dangerous_xss_unsanitized_input


(call_expression
function: (member_expression
object: (identifier) @elem
property: (property_identifier) @write)
arguments: (arguments
(identifier) @id)
(#match? @elem "^(element|elem|node|div|span|container|wrapper|section|component)$")
(#eq? @write "write")
(#match? @id "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$")) @dangerous_xss_unsanitized_input


(assignment_expression
left: (member_expression
object: (identifier) @doc
property: (property_identifier) @prop)
right: (identifier) @id
(#eq? @doc "document")
(#match? @prop "^(innerHTML|outerHTML)$")
(#match? @id "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$")) @dangerous_xss_unsanitized_input


(assignment_expression
left: (member_expression
object: (call_expression
function: (member_expression
object: (identifier) @doc
(#eq? @doc "document")
property: (property_identifier) @getById)
arguments: (arguments
(string)))
property: (property_identifier) @prop)
right: (identifier) @id
(#eq? @getById "getElementById")
(#match? @prop "^(innerHTML|outerHTML)$")
(#match? @id "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$")) @dangerous_xss_unsanitized_input


(assignment_expression
left: (identifier) @htmlVar
right: (template_string
(template_substitution
(identifier) @id)
(#match? @id "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$"))) @dangerous_xss_unsanitized_input


(assignment_expression
left: (member_expression
object: (call_expression
function: (member_expression
object: (identifier) @doc
(#eq? @doc "document")
property: (property_identifier) @getById)
arguments: (arguments
(string)))
property: (property_identifier) @prop)
right: (identifier) @htmlVar
(#eq? @getById "getElementById")
(#match? @prop "^(innerHTML|outerHTML)$")) @dangerous_xss_unsanitized_input


(assignment_expression
left: (identifier) @htmlVar
right: (template_string))

(assignment_expression
left: (member_expression
object: (call_expression
function: (member_expression
object: (identifier) @doc
(#eq? @doc "document")
property: (property_identifier) @getById)
arguments: (arguments))
property: (property_identifier) @prop)
right: (identifier) @htmlVar
(#eq? @getById "getElementById")
(#match? @prop "^(innerHTML|outerHTML)$")) @dangerous_xss_unsanitized_input


(assignment_expression
left: (identifier) @htmlVar
right: (binary_expression
left: (_)
operator: "+"
right: (identifier) @id)
(#match? @id "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$")) @dangerous_xss_unsanitized_input


(assignment_expression
left: (identifier) @htmlVar
right: (binary_expression
left: (identifier) @id
operator: "+"
right: (_))
(#match? @id "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$")) @dangerous_xss_unsanitized_input


(assignment_expression
left: (member_expression
object: (identifier) @elem
property: (property_identifier) @prop)
right: (identifier) @id
(#match? @elem "^(element|elem|node|div|span|container|wrapper|section|component)$")
(#match? @prop "^(innerHTML|outerHTML)$")
(#match? @id "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$")) @dangerous_xss_unsanitized_input


(assignment_expression
left: (member_expression
object: (identifier) @elem
property: (property_identifier) @prop)
right: (binary_expression
(binary_expression
left: (string)
operator: "+"
right: (identifier) @input)
operator: "+"
right: (string))
(#match? @elem "^(document|element|elem|node|div|span|container|wrapper|section|component)$")
(#match? @prop "^(innerHTML|outerHTML)$")
(#match? @input "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$")) @dangerous_xss_unsanitized_input


(assignment_expression
left: (member_expression
object: (member_expression
object: (identifier) @doc
(#match? @doc "^(document|element|elem|node|div|span|container|wrapper|section|component)$")
property: (property_identifier) @body
(#match? @body "^(body|innerHTML|outerHTML)$"))
property: (property_identifier) @prop
(#eq? @prop "innerHTML"))
right: (binary_expression)) @dangerous_xss_unsanitized_input


(assignment_expression
left: (member_expression
object: (identifier) @elem
property: (property_identifier) @prop)
right: (binary_expression
left: (string)
operator: "+"
right: (identifier) @input)
(#match? @elem "^(document|element|elem|node|div|span|container|wrapper|section|component)$")
(#match? @prop "^(innerHTML|outerHTML)$")
(#match? @input "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$")) @dangerous_xss_unsanitized_input


(assignment_expression
left: (member_expression
object: (identifier) @elem
property: (property_identifier) @prop)
right: (binary_expression
left: (identifier) @input
operator: "+"
right: (string))
(#match? @elem "^(document|element|elem|node|div|span|container|wrapper|section|component)$")
(#match? @prop "^(innerHTML|outerHTML)$")
(#match? @input "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$")) @dangerous_xss_unsanitized_input


(assignment_expression
left: (member_expression
property: (property_identifier) @prop
(#match? @prop "^(innerHTML|outerHTML)$"))
right: (call_expression
function: (identifier) @func
(#match? @func "^(getUserInput|getInput|fetchData|getData|retrieveData|loadData|parseData|parseInput|processInput)$"))) @dangerous_xss_unsanitized_input


(call_expression
function: (member_expression
object: (identifier) @element
property: (property_identifier) @insertAdjacentHTML)
arguments: (arguments
(string)
(identifier) @id)
(#eq? @insertAdjacentHTML "insertAdjacentHTML")
(#match? @id "^(user_input|userInput|input|data|content|payload|untrustedData|rawData|response|responseData|formData|userData)$")) @dangerous_xss_unsanitized_input


(call_expression
function: (member_expression
object: (identifier) @element
property: (property_identifier) @insertAdjacentHTML)
arguments: (arguments
(string)
(template_string))
(#eq? @insertAdjacentHTML "insertAdjacentHTML")) @dangerous_xss_unsanitized_input


(assignment_expression
left: (member_expression
property: (property_identifier) @prop
(#match? @prop "^(innerHTML|outerHTML)$"))
right: (template_string)) @dangerous_xss_unsanitized_input
]
exclude:
- "node_modules/**"
- "dist/**"
- "vendor/**"
- "test/**"
filters:
- pattern-not-inside: |
(try_statement
body: (statement_block)
handler: (catch_clause))
- pattern-not-inside: |
(call_expression
function: (identifier) @sanitizer
(#match? @sanitizer "^(sanitizeHTML|DOMPurify\.sanitize|escapeHTML|encodeHTML|htmlEncode)$"))
description: |
Direct DOM manipulation with unsanitized input exposes XSS vulnerabilities.
Always use safe methods like textContent or DOM sanitization libraries such as DOMPurify.