Skip to content

Commit dad8e9e

Browse files
committed
Add XSS prevention: security utilities, tests, and documentation
1 parent 87e24b5 commit dad8e9e

8 files changed

Lines changed: 1430 additions & 1 deletion

File tree

frontend/SECURITY_PATTERNS.md

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
# Security Patterns for Safe Rendering
2+
3+
This document outlines security best practices for the frontend application to prevent XSS and other injection attacks.
4+
5+
## Core Principles
6+
7+
### 1. Trust React's Automatic Escaping
8+
9+
React automatically escapes all values rendered in JSX:
10+
11+
```tsx
12+
// ✅ SAFE - React escapes the value
13+
const userInput = '<script>alert(1)</script>';
14+
<div>{userInput}</div>
15+
// Renders: &lt;script&gt;alert(1)&lt;/script&gt;
16+
17+
// ✅ SAFE - Attributes are also escaped
18+
<div title={userInput}>Content</div>
19+
```
20+
21+
### 2. Never Use Dangerous APIs
22+
23+
```tsx
24+
// ❌ NEVER DO THIS
25+
<div dangerouslySetInnerHTML={{ __html: userInput }} />
26+
27+
// ❌ NEVER DO THIS
28+
element.innerHTML = userInput;
29+
30+
// ❌ NEVER DO THIS
31+
eval(userInput);
32+
new Function(userInput)();
33+
```
34+
35+
### 3. Validate External Data
36+
37+
Always validate data from external sources:
38+
39+
```typescript
40+
import { isValidTransactionHash, isValidStellarAddress } from './lib/security';
41+
42+
// ✅ SAFE - Validate before using
43+
if (isValidTransactionHash(hash)) {
44+
const url = getStellarExplorerUrl(hash);
45+
}
46+
47+
// ✅ SAFE - Validate addresses
48+
if (isValidStellarAddress(address)) {
49+
// Use address
50+
}
51+
```
52+
53+
### 4. Sanitize External URLs
54+
55+
```typescript
56+
import { sanitizeExternalLink } from './lib/security';
57+
58+
// ✅ SAFE - Sanitize before using
59+
const safeUrl = sanitizeExternalLink(userProvidedUrl);
60+
<a href={safeUrl} target="_blank" rel="noopener noreferrer">Link</a>
61+
```
62+
63+
## Common Patterns
64+
65+
### Rendering API Data
66+
67+
```tsx
68+
// ✅ SAFE - TypeScript interfaces + React escaping
69+
interface Transaction {
70+
id: string;
71+
amount: string;
72+
asset: string;
73+
}
74+
75+
function TransactionRow({ tx }: { tx: Transaction }) {
76+
return (
77+
<tr>
78+
<td>{tx.id}</td>
79+
<td>{tx.amount}</td>
80+
<td>{tx.asset}</td>
81+
</tr>
82+
);
83+
}
84+
```
85+
86+
### Rendering User Input
87+
88+
```tsx
89+
// ✅ SAFE - Controlled inputs
90+
function AmountInput() {
91+
const [amount, setAmount] = useState('');
92+
93+
return (
94+
<input
95+
value={amount}
96+
onChange={(e) => setAmount(e.target.value)}
97+
/>
98+
);
99+
}
100+
```
101+
102+
### Building URLs
103+
104+
```tsx
105+
import { getStellarExplorerUrl } from './lib/security';
106+
107+
// ✅ SAFE - Use security utilities
108+
function TransactionLink({ hash }: { hash: string }) {
109+
const url = getStellarExplorerUrl(hash, 'testnet');
110+
111+
return (
112+
<a href={url} target="_blank" rel="noopener noreferrer">
113+
View Transaction
114+
</a>
115+
);
116+
}
117+
118+
// ❌ UNSAFE - Direct concatenation without validation
119+
function UnsafeLink({ hash }: { hash: string }) {
120+
return <a href={`https://example.com/tx/${hash}`}>Link</a>;
121+
}
122+
```
123+
124+
### Displaying Error Messages
125+
126+
```tsx
127+
// ✅ SAFE - Error objects are typed
128+
function ErrorDisplay({ error }: { error: Error }) {
129+
return (
130+
<div className="error">
131+
<p>{error.message}</p>
132+
</div>
133+
);
134+
}
135+
```
136+
137+
### Rendering Lists
138+
139+
```tsx
140+
// ✅ SAFE - Array.map with React escaping
141+
function AssetList({ assets }: { assets: string[] }) {
142+
return (
143+
<ul>
144+
{assets.map((asset, i) => (
145+
<li key={i}>{asset}</li>
146+
))}
147+
</ul>
148+
);
149+
}
150+
```
151+
152+
## Anti-Patterns to Avoid
153+
154+
### ❌ String Concatenation for HTML
155+
156+
```tsx
157+
// ❌ NEVER DO THIS
158+
const html = '<div>' + userInput + '</div>';
159+
element.innerHTML = html;
160+
161+
// ✅ DO THIS INSTEAD
162+
<div>{userInput}</div>
163+
```
164+
165+
### ❌ Unvalidated External URLs
166+
167+
```tsx
168+
// ❌ UNSAFE
169+
<a href={userProvidedUrl}>Link</a>
170+
171+
// ✅ SAFE
172+
import { sanitizeExternalLink } from './lib/security';
173+
<a href={sanitizeExternalLink(userProvidedUrl)}>Link</a>
174+
```
175+
176+
### ❌ Dynamic Event Handlers from Strings
177+
178+
```tsx
179+
// ❌ NEVER DO THIS
180+
<button onClick={eval(userInput)}>Click</button>
181+
182+
// ✅ DO THIS INSTEAD
183+
<button onClick={() => handleClick()}>Click</button>
184+
```
185+
186+
### ❌ Unvalidated Data in Template Literals
187+
188+
```tsx
189+
// ❌ POTENTIALLY UNSAFE
190+
const url = `https://api.example.com/${userInput}`;
191+
192+
// ✅ SAFE - Validate first
193+
import { isAlphanumericSafe } from './lib/security';
194+
if (isAlphanumericSafe(userInput)) {
195+
const url = `https://api.example.com/${userInput}`;
196+
}
197+
```
198+
199+
## Security Utilities
200+
201+
### Available Functions
202+
203+
```typescript
204+
// Transaction hash validation
205+
isValidTransactionHash(hash: string): boolean
206+
207+
// Stellar address validation
208+
isValidStellarAddress(address: string): boolean
209+
210+
// URL sanitization
211+
sanitizeExternalUrl(url: string, allowedDomains: string[]): string
212+
sanitizeExternalLink(url: string): string
213+
214+
// Safe URL construction
215+
getStellarExplorerUrl(hash: string, network: 'testnet' | 'mainnet'): string
216+
217+
// HTML escaping (rarely needed in React)
218+
escapeHtml(text: string): string
219+
220+
// Alphanumeric validation
221+
isAlphanumericSafe(text: string): boolean
222+
223+
// Safe truncation
224+
truncateSafe(text: string, maxLength: number): string
225+
```
226+
227+
### Usage Examples
228+
229+
```typescript
230+
import {
231+
isValidTransactionHash,
232+
getStellarExplorerUrl,
233+
sanitizeExternalLink,
234+
} from './lib/security';
235+
236+
// Validate transaction hash
237+
if (isValidTransactionHash(hash)) {
238+
const url = getStellarExplorerUrl(hash, 'testnet');
239+
window.open(url, '_blank');
240+
}
241+
242+
// Sanitize external link
243+
const safeUrl = sanitizeExternalLink(userUrl);
244+
if (safeUrl !== '#') {
245+
// URL is safe to use
246+
}
247+
```
248+
249+
## Testing
250+
251+
### Run XSS Prevention Tests
252+
253+
```bash
254+
npm test -- xss-prevention
255+
```
256+
257+
### Run Security Utility Tests
258+
259+
```bash
260+
npm test -- security.test
261+
```
262+
263+
### Test Coverage
264+
265+
The test suite includes:
266+
- 17+ common XSS attack vectors
267+
- React component rendering tests
268+
- Form input validation tests
269+
- URL sanitization tests
270+
- Integration tests
271+
272+
## ESLint Security Rules
273+
274+
The following ESLint rules are enforced:
275+
276+
```javascript
277+
{
278+
'no-eval': 'error',
279+
'no-implied-eval': 'error',
280+
'no-new-func': 'error',
281+
'no-script-url': 'error',
282+
}
283+
```
284+
285+
## Content Security Policy
286+
287+
When deploying, add these CSP headers:
288+
289+
```
290+
Content-Security-Policy:
291+
default-src 'self';
292+
script-src 'self';
293+
style-src 'self' 'unsafe-inline';
294+
img-src 'self' data: https:;
295+
connect-src 'self' https://horizon-testnet.stellar.org https://horizon.stellar.org;
296+
frame-ancestors 'none';
297+
base-uri 'self';
298+
form-action 'self';
299+
```
300+
301+
## Checklist for New Features
302+
303+
When adding new features, verify:
304+
305+
- [ ] No use of `dangerouslySetInnerHTML`
306+
- [ ] No use of `innerHTML`, `outerHTML`, or `insertAdjacentHTML`
307+
- [ ] No use of `eval()`, `Function()`, or `setTimeout(string)`
308+
- [ ] External URLs are validated with `sanitizeExternalLink()`
309+
- [ ] Transaction hashes are validated with `isValidTransactionHash()`
310+
- [ ] User input is rendered through React JSX (automatic escaping)
311+
- [ ] External links have `rel="noopener noreferrer"`
312+
- [ ] Tests cover malicious input scenarios
313+
314+
## Resources
315+
316+
- [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
317+
- [React Security Best Practices](https://react.dev/learn/writing-markup-with-jsx)
318+
- [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
319+
- [Stellar Security Guidelines](https://developers.stellar.org/docs/building-apps/security)
320+
321+
## Questions?
322+
323+
If you're unsure whether a pattern is safe, ask yourself:
324+
325+
1. Does this use any dangerous APIs? (`dangerouslySetInnerHTML`, `eval`, `innerHTML`)
326+
2. Is external data validated before use?
327+
3. Are URLs sanitized before rendering?
328+
4. Is React's automatic escaping being bypassed?
329+
330+
If the answer to any of these is "yes" or "maybe", review this document or consult the security utilities.

0 commit comments

Comments
 (0)