@sun-asterisk/sunlint
Version:
☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards
230 lines (229 loc) • 6.67 kB
JSON
{
"rule": {
"id": "S022",
"name": "Escape data properly based on output context",
"description": "Ensure data is properly escaped or sanitized based on the output context (HTML, JavaScript, CSS, URL) to prevent Cross-Site Scripting (XSS) attacks. Different contexts require different escaping methods.",
"category": "security",
"severity": "error",
"languages": ["typescript", "javascript"],
"frameworks": ["express", "nestjs", "react", "vue", "angular", "node"],
"version": "1.0.0",
"status": "stable",
"tags": ["security", "xss", "escaping", "sanitization", "owasp", "injection"],
"references": [
"https://owasp.org/www-community/attacks/xss/",
"https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html",
"https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html",
"https://portswigger.net/web-security/cross-site-scripting",
"https://cwe.mitre.org/data/definitions/79.html"
]
},
"configuration": {
"contexts": {
"html": {
"dangerousMethods": [
"innerHTML",
"outerHTML",
"insertAdjacentHTML",
"document.write",
"document.writeln",
"v-html",
"dangerouslySetInnerHTML"
],
"requireEscaping": true,
"severity": "error"
},
"javascript": {
"dangerousMethods": [
"eval",
"Function",
"setTimeout",
"setInterval",
"execScript"
],
"requireEscaping": true,
"severity": "error"
},
"url": {
"dangerousMethods": [
"location.href",
"window.location",
"location.assign",
"location.replace",
"window.open"
],
"requireValidation": true,
"severity": "warning"
},
"attribute": {
"dangerousAttributes": [
"onclick",
"onload",
"onerror",
"onmouseover",
"href",
"src"
],
"requireEscaping": true,
"severity": "error"
}
},
"userInputSources": [
"req.body",
"req.query",
"req.params",
"request.body",
"request.query",
"request.params",
"localStorage",
"sessionStorage",
"window.location",
"location.search",
"location.hash",
"URLSearchParams",
"document.cookie",
"window.name",
"postMessage"
],
"safeEscapingFunctions": [
"escape",
"escapeHtml",
"sanitize",
"DOMPurify.sanitize",
"textContent",
"innerText",
"setAttribute",
"encodeURIComponent",
"encodeURI",
"validator.escape",
"xss.filterXSS"
],
"safeFrameworkMethods": {
"react": [
"textContent",
"children",
"{variable}"
],
"vue": [
"{{ variable }}",
"v-text"
],
"angular": [
"{{ variable }}",
"[textContent]"
]
}
},
"examples": {
"violations": [
{
"description": "Unescaped user input in innerHTML",
"code": "element.innerHTML = userInput;",
"context": "html"
},
{
"description": "User input in eval without validation",
"code": "eval(req.query.code);",
"context": "javascript"
},
{
"description": "Unvalidated URL from user input",
"code": "window.location = req.query.redirect;",
"context": "url"
},
{
"description": "React dangerouslySetInnerHTML without sanitization",
"code": "<div dangerouslySetInnerHTML={{__html: userInput}} />",
"context": "html"
},
{
"description": "Vue v-html directive with user input",
"code": "<div v-html=\"userInput\"></div>",
"context": "html"
},
{
"description": "Dynamic event handler with user input",
"code": "element.setAttribute('onclick', userInput);",
"context": "attribute"
}
],
"fixes": [
{
"description": "Use textContent instead of innerHTML",
"code": "element.textContent = userInput;",
"context": "html"
},
{
"description": "Sanitize HTML with DOMPurify",
"code": "element.innerHTML = DOMPurify.sanitize(userInput);",
"context": "html"
},
{
"description": "Never use eval with user input",
"code": "// Avoid eval entirely, use JSON.parse or safe alternatives",
"context": "javascript"
},
{
"description": "Validate and whitelist URLs",
"code": "const allowedHosts = ['example.com'];\nconst url = new URL(req.query.redirect);\nif (allowedHosts.includes(url.hostname)) {\n window.location = url.href;\n}",
"context": "url"
},
{
"description": "React with proper sanitization",
"code": "<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(userInput)}} />",
"context": "html"
},
{
"description": "Vue using v-text for safe output",
"code": "<div v-text=\"userInput\"></div>",
"context": "html"
}
]
},
"testing": {
"testCases": [
{
"name": "innerHTML_with_user_input",
"type": "violation",
"description": "Using innerHTML with unsanitized user input"
},
{
"name": "eval_with_user_input",
"type": "violation",
"description": "Using eval with user input"
},
{
"name": "location_with_user_input",
"type": "violation",
"description": "Setting location with unvalidated user input"
},
{
"name": "textContent_safe_output",
"type": "clean",
"description": "Using textContent for safe output"
},
{
"name": "dompurify_sanitization",
"type": "clean",
"description": "Using DOMPurify to sanitize HTML"
},
{
"name": "url_validation",
"type": "clean",
"description": "Validating URLs before redirect"
}
]
},
"performance": {
"complexity": "O(n)",
"description": "Linear complexity based on number of DOM manipulation and output operations in the source code"
},
"owaspMapping": {
"category": "A03:2021 – Injection",
"subcategories": [
"A07:2021 – Identification and Authentication Failures"
],
"cweId": "CWE-79",
"description": "Validates that all output is properly escaped or sanitized based on context to prevent XSS attacks"
}
}