@sun-asterisk/sunlint 1.3.26 → 1.3.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/rules/enhanced-rules-registry.json +99 -16
- package/package.json +1 -1
- package/rules/common/C029_catch_block_logging/analyzer.js +47 -12
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +35 -15
- package/rules/security/S003_open_redirect_protection/README.md +371 -0
- package/rules/security/S003_open_redirect_protection/analyzer.js +135 -0
- package/rules/security/S003_open_redirect_protection/config.json +58 -0
- package/rules/security/S003_open_redirect_protection/symbol-based-analyzer.js +884 -0
- package/rules/security/S004_sensitive_data_logging/analyzer.js +135 -0
- package/rules/security/S004_sensitive_data_logging/config.json +62 -0
- package/rules/security/S004_sensitive_data_logging/symbol-based-analyzer.js +592 -0
- package/rules/security/S012_hardcoded_secrets/analyzer.js +149 -0
- package/rules/security/S012_hardcoded_secrets/config.json +75 -0
- package/rules/security/S012_hardcoded_secrets/symbol-based-analyzer.js +1204 -0
- package/rules/security/S019_smtp_injection_protection/analyzer.js +120 -0
- package/rules/security/S019_smtp_injection_protection/config.json +35 -0
- package/rules/security/S019_smtp_injection_protection/symbol-based-analyzer.js +687 -0
- package/rules/security/S022_escape_output_context/README.md +254 -0
- package/rules/security/S022_escape_output_context/analyzer.js +510 -0
- package/rules/security/S022_escape_output_context/config.json +229 -0
- package/rules/security/S023_no_json_injection/analyzer.js +15 -0
- package/rules/security/S023_no_json_injection/ast-analyzer.js +18 -3
- package/rules/security/S023_no_json_injection/config.json +133 -0
- package/rules/security/S024_xpath_xxe_protection/regex-based-analyzer.js +41 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +67 -8
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +29 -6
- package/rules/security/S029_csrf_protection/config.json +127 -0
- package/rules/security/S030_directory_browsing_protection/regex-based-analyzer.js +160 -28
- package/rules/security/S030_directory_browsing_protection/symbol-based-analyzer.js +81 -19
- package/rules/security/S031_secure_session_cookies/analyzer.js +20 -2
- package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +100 -0
- package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +8 -1
- package/rules/security/S032_httponly_session_cookies/analyzer.js +2 -2
- package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +115 -0
- package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +39 -10
- package/rules/security/S036_lfi_rfi_protection/analyzer.js +224 -0
- package/rules/security/S036_lfi_rfi_protection/config.json +20 -0
- package/rules/security/S040_session_fixation_protection/analyzer.js +153 -0
- package/rules/security/S040_session_fixation_protection/config.json +20 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/README.md +83 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/analyzer.js +153 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/config.json +41 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/symbol-based-analyzer.js +1139 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# S022 - Escape Data Properly Based on Output Context
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
**Rule ID:** S022
|
|
6
|
+
**Category:** Security
|
|
7
|
+
**Severity:** Error
|
|
8
|
+
**OWASP:** A03:2021 – Injection (XSS)
|
|
9
|
+
**CWE:** CWE-79 - Improper Neutralization of Input During Web Page Generation
|
|
10
|
+
|
|
11
|
+
## Description
|
|
12
|
+
|
|
13
|
+
This rule ensures that all data output to different contexts (HTML, JavaScript, URL, CSS, attributes) is properly escaped or sanitized to prevent Cross-Site Scripting (XSS) attacks. Different output contexts require different escaping mechanisms.
|
|
14
|
+
|
|
15
|
+
XSS vulnerabilities occur when untrusted data is included in a web page without proper validation or escaping, allowing attackers to inject malicious scripts that execute in victims' browsers.
|
|
16
|
+
|
|
17
|
+
## Why This Matters
|
|
18
|
+
|
|
19
|
+
### Security Impact
|
|
20
|
+
|
|
21
|
+
- **Data Theft**: Attackers can steal sensitive information (cookies, session tokens, credentials)
|
|
22
|
+
- **Account Hijacking**: Session tokens can be stolen for account takeover
|
|
23
|
+
- **Malware Distribution**: Inject scripts that download malware
|
|
24
|
+
- **Defacement**: Modify page content to damage reputation
|
|
25
|
+
- **Phishing**: Redirect users to malicious sites
|
|
26
|
+
|
|
27
|
+
### Context-Specific Escaping
|
|
28
|
+
|
|
29
|
+
Different contexts require different escaping methods:
|
|
30
|
+
|
|
31
|
+
1. **HTML Context**: `<div>{data}</div>` → Escape `<`, `>`, `&`, `"`, `'`
|
|
32
|
+
2. **JavaScript Context**: `<script>var x = '{data}'</script>` → JSON encoding
|
|
33
|
+
3. **URL Context**: `<a href="{data}">` → URL encoding + validation
|
|
34
|
+
4. **CSS Context**: `<style>{data}</style>` → CSS escaping
|
|
35
|
+
5. **Attribute Context**: `<div title="{data}">` → Attribute escaping
|
|
36
|
+
|
|
37
|
+
## What It Detects
|
|
38
|
+
|
|
39
|
+
### 1. HTML Context Violations
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
// ❌ BAD: Unsafe innerHTML with user input
|
|
43
|
+
element.innerHTML = req.query.name;
|
|
44
|
+
element.outerHTML = userInput;
|
|
45
|
+
document.write(req.body.content);
|
|
46
|
+
|
|
47
|
+
// ❌ BAD: React dangerouslySetInnerHTML without sanitization
|
|
48
|
+
<div dangerouslySetInnerHTML={{__html: userInput}} />
|
|
49
|
+
|
|
50
|
+
// ❌ BAD: Vue v-html without sanitization
|
|
51
|
+
<div v-html="userInput"></div>
|
|
52
|
+
|
|
53
|
+
// ✅ GOOD: Use textContent for plain text
|
|
54
|
+
element.textContent = req.query.name;
|
|
55
|
+
|
|
56
|
+
// ✅ GOOD: Sanitize HTML with DOMPurify
|
|
57
|
+
element.innerHTML = DOMPurify.sanitize(userInput);
|
|
58
|
+
|
|
59
|
+
// ✅ GOOD: React with sanitization
|
|
60
|
+
<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(userInput)}} />
|
|
61
|
+
|
|
62
|
+
// ✅ GOOD: Vue with v-text
|
|
63
|
+
<div v-text="userInput"></div>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. JavaScript Context Violations
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
// ❌ BAD: eval with user input
|
|
70
|
+
eval(req.query.code);
|
|
71
|
+
new Function(userInput)();
|
|
72
|
+
setTimeout(req.body.script, 1000);
|
|
73
|
+
|
|
74
|
+
// ❌ BAD: Even dynamic eval is dangerous
|
|
75
|
+
const code = getCodeFromSomewhere();
|
|
76
|
+
eval(code);
|
|
77
|
+
|
|
78
|
+
// ✅ GOOD: Never use eval - use JSON.parse for data
|
|
79
|
+
const data = JSON.parse(jsonString);
|
|
80
|
+
|
|
81
|
+
// ✅ GOOD: Use safe alternatives
|
|
82
|
+
const func = functionMap[safeKey];
|
|
83
|
+
func();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3. URL Context Violations
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// ❌ BAD: Unvalidated URL redirect (Open Redirect)
|
|
90
|
+
window.location = req.query.redirect;
|
|
91
|
+
location.href = userInput;
|
|
92
|
+
window.open(req.query.url);
|
|
93
|
+
|
|
94
|
+
// ✅ GOOD: Validate URLs against whitelist
|
|
95
|
+
const allowedHosts = ['example.com', 'trusted.com'];
|
|
96
|
+
const url = new URL(req.query.redirect);
|
|
97
|
+
if (allowedHosts.includes(url.hostname)) {
|
|
98
|
+
window.location = url.href;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ✅ GOOD: Use relative URLs only
|
|
102
|
+
if (redirectUrl.startsWith('/')) {
|
|
103
|
+
window.location = redirectUrl;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 4. Event Handler Violations
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// ❌ BAD: Dynamic event handler with user input
|
|
111
|
+
element.setAttribute('onclick', userInput);
|
|
112
|
+
element.setAttribute('onerror', req.query.handler);
|
|
113
|
+
|
|
114
|
+
// ✅ GOOD: Use addEventListener
|
|
115
|
+
element.addEventListener('click', function() {
|
|
116
|
+
// Safe handler logic
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ✅ GOOD: Use data attributes
|
|
120
|
+
element.dataset.action = userInput;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Framework-Specific Guidance
|
|
124
|
+
|
|
125
|
+
### React
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
// ❌ Avoid dangerouslySetInnerHTML
|
|
129
|
+
<div dangerouslySetInnerHTML={{__html: userInput}} />
|
|
130
|
+
|
|
131
|
+
// ✅ Use children or textContent
|
|
132
|
+
<div>{userInput}</div>
|
|
133
|
+
|
|
134
|
+
// ✅ If HTML is needed, sanitize it
|
|
135
|
+
import DOMPurify from 'dompurify';
|
|
136
|
+
<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(html)}} />
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Vue
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
// ❌ Avoid v-html with user input
|
|
143
|
+
<div v-html="userInput"></div>
|
|
144
|
+
|
|
145
|
+
// ✅ Use interpolation or v-text
|
|
146
|
+
<div>{{ userInput }}</div>
|
|
147
|
+
<div v-text="userInput"></div>
|
|
148
|
+
|
|
149
|
+
// ✅ If HTML is needed, sanitize it
|
|
150
|
+
<div v-html="$sanitize(html)"></div>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Angular
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
// ❌ Avoid [innerHTML] with user input
|
|
157
|
+
<div [innerHTML]="userInput"></div>
|
|
158
|
+
|
|
159
|
+
// ✅ Use interpolation
|
|
160
|
+
<div>{{ userInput }}</div>
|
|
161
|
+
|
|
162
|
+
// ✅ Use DomSanitizer if HTML is needed
|
|
163
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
164
|
+
this.safeHtml = this.sanitizer.sanitize(SecurityContext.HTML, html);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Recommended Sanitization Libraries
|
|
168
|
+
|
|
169
|
+
1. **DOMPurify** (Browser & Node.js)
|
|
170
|
+
```javascript
|
|
171
|
+
import DOMPurify from 'dompurify';
|
|
172
|
+
const clean = DOMPurify.sanitize(dirty);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
2. **xss** (Node.js)
|
|
176
|
+
```javascript
|
|
177
|
+
const xss = require('xss');
|
|
178
|
+
const clean = xss(dirty);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
3. **validator.js** (Node.js)
|
|
182
|
+
```javascript
|
|
183
|
+
const validator = require('validator');
|
|
184
|
+
const escaped = validator.escape(input);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Safe Alternatives
|
|
188
|
+
|
|
189
|
+
| Unsafe Method | Safe Alternative |
|
|
190
|
+
|--------------|------------------|
|
|
191
|
+
| `innerHTML` | `textContent` or `innerText` |
|
|
192
|
+
| `outerHTML` | `textContent` |
|
|
193
|
+
| `document.write()` | DOM manipulation methods |
|
|
194
|
+
| `eval()` | `JSON.parse()` |
|
|
195
|
+
| `setTimeout(string)` | `setTimeout(function)` |
|
|
196
|
+
| `dangerouslySetInnerHTML` | React children or DOMPurify |
|
|
197
|
+
| `v-html` | `v-text` or sanitization |
|
|
198
|
+
|
|
199
|
+
## How to Fix
|
|
200
|
+
|
|
201
|
+
1. **Identify the Context**: Determine where the data will be output (HTML, JS, URL, etc.)
|
|
202
|
+
2. **Choose Appropriate Method**:
|
|
203
|
+
- HTML: Use `textContent` or sanitize with DOMPurify
|
|
204
|
+
- JavaScript: Avoid dynamic code execution
|
|
205
|
+
- URL: Validate and whitelist
|
|
206
|
+
3. **Apply Escaping/Sanitization**: Use context-appropriate escaping
|
|
207
|
+
4. **Test**: Verify XSS payloads don't execute
|
|
208
|
+
|
|
209
|
+
## Testing
|
|
210
|
+
|
|
211
|
+
Create test files with common XSS payloads:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
const testPayloads = [
|
|
215
|
+
'<script>alert("XSS")</script>',
|
|
216
|
+
'<img src=x onerror=alert("XSS")>',
|
|
217
|
+
'javascript:alert("XSS")',
|
|
218
|
+
'<svg onload=alert("XSS")>',
|
|
219
|
+
'"><script>alert("XSS")</script>',
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
// Test that these are properly escaped/sanitized
|
|
223
|
+
testPayloads.forEach(payload => {
|
|
224
|
+
const result = sanitize(payload);
|
|
225
|
+
assert(!result.includes('<script'));
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## References
|
|
230
|
+
|
|
231
|
+
- [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
|
|
232
|
+
- [OWASP DOM XSS Prevention](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html)
|
|
233
|
+
- [DOMPurify Documentation](https://github.com/cure53/DOMPurify)
|
|
234
|
+
- [CWE-79: Improper Neutralization of Input](https://cwe.mitre.org/data/definitions/79.html)
|
|
235
|
+
|
|
236
|
+
## Configuration
|
|
237
|
+
|
|
238
|
+
This rule can be configured in `.sunlint.json`:
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"rules": {
|
|
243
|
+
"S022": {
|
|
244
|
+
"enabled": true,
|
|
245
|
+
"severity": "error",
|
|
246
|
+
"contexts": {
|
|
247
|
+
"html": { "severity": "error" },
|
|
248
|
+
"javascript": { "severity": "error" },
|
|
249
|
+
"url": { "severity": "warning" }
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|