- name
- web-security-audit
- description
- Expert guidance on identifying and mitigating common web vulnerabilities from a bug hunter's perspective. Covers access control, XSS, CSRF, SSRF, insecure file uploads, and JWT security with detailed protection strategies, implementation patterns, and verification checklists.
- license
- Complete terms in file LICENSE
# Secure Coding Guide for Web Applications
## Overview
This guide provides comprehensive secure coding practices for web applications. As an AI assistant, your role is to approach code from a **bug hunter's perspective** and make applications **as secure as possible** without breaking functionality.
**Key Principles:**
- Defense in depth: Never rely on a single security control
- Fail securely: When something fails, fail closed (deny access)
- Least privilege: Grant minimum permissions necessary
- Input validation: Never trust user input, validate everything server-side
- Output encoding: Encode data appropriately for the context it's rendered in
---
## Access Control Issues
Access control vulnerabilities occur when users can access resources or perform actions beyond their intended permissions.
### Core Requirements
For **every data point and action** that requires authentication:
1. **User-Level Authorization**
- Each user must only access/modify their own data
- No user should access data from other users or organizations
- Always verify ownership at the data layer, not just the route level
2. **Use UUIDs Instead of Sequential IDs**
- Use UUIDv4 or similar non-guessable identifiers
- Exception: Only use sequential IDs if explicitly requested by user
3. **Account Lifecycle Handling**
- When a user is removed from an organization: immediately revoke all access tokens and sessions
- When an account is deleted/deactivated: invalidate all active sessions and API keys
- Implement token revocation lists or short-lived tokens with refresh mechanisms
### Authorization Checks Checklist
- [ ] Verify user owns the resource on every request (don't trust client-side data)
- [ ] Check organization membership for multi-tenant apps
- [ ] Validate role permissions for role-based actions
- [ ] Re-validate permissions after any privilege change
- [ ] Check parent resource ownership (e.g., if accessing a comment, verify user owns the parent post)
### Common Pitfalls to Avoid
- **IDOR (Insecure Direct Object Reference)**: Always verify the requesting user has permission to access the requested resource ID
- **Privilege Escalation**: Validate role changes server-side; never trust role info from client
- **Horizontal Access**: User A accessing User B's resources with the same privilege level
- **Vertical Access**: Regular user accessing admin functionality
- **Mass Assignment**: Filter which fields users can update; don't blindly accept all request body fields
### Implementation Pattern
```
# Pseudocode for secure resource access
function getResource(resourceId, currentUser):
resource = database.find(resourceId)
if resource is null:
return 404 # Don't reveal if resource exists
if resource.ownerId != currentUser.id:
if not currentUser.hasOrgAccess(resource.orgId):
return 404 # Return 404, not 403, to prevent enumeration
return resource
```
---
## Client-Side Bugs
### Cross-Site Scripting (XSS)
Every input controllable by the user—whether directly or indirectly—must be sanitized against XSS.
#### Input Sources to Protect
**Direct Inputs:**
- Form fields (email, name, bio, comments, etc.)
- Search queries
- File names during upload
- Rich text editors / WYSIWYG content
**Indirect Inputs:**
- URL parameters and query strings
- URL fragments (hash values)
- HTTP headers used in the application (Referer, User-Agent if displayed)
- Data from third-party APIs displayed to users
- WebSocket messages
- postMessage data from iframes
- LocalStorage/SessionStorage values if rendered
**Often Overlooked:**
- Error messages that reflect user input
- PDF/document generators that accept HTML
- Email templates with user data
- Log viewers in admin panels
- JSON responses rendered as HTML
- SVG file uploads (can contain JavaScript)
- Markdown rendering (if allowing HTML)
#### Protection Strategies
1. **Output Encoding** (Context-Specific)
- HTML context: HTML entity encode (`<` → `<`)
- JavaScript context: JavaScript escape
- URL context: URL encode
- CSS context: CSS escape
- Use framework's built-in escaping (React's JSX, Vue's {{ }}, etc.)
2. **Content Security Policy (CSP)**
```
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.yourdomain.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
```
- Avoid `'unsafe-inline'` and `'unsafe-eval'` for scripts
- Use nonces or hashes for inline scripts when necessary
- Report violations: `report-uri /csp-report`
3. **Input Sanitization**
- Use established libraries (DOMPurify for HTML)
- Whitelist allowed tags/attributes for rich text
- Strip or encode dangerous patterns
4. **Additional Headers**
- `X-Content-Type-Options: nosniff`
- `X-Frame-Options: DENY` (or use CSP frame-ancestors)
---
### Cross-Site Request Forgery (CSRF)
Every state-changing endpoint must be protected against CSRF attacks.
#### Endpoints Requiring CSRF Protection
**Authenticated Actions:**
- All POST, PUT, PATCH, DELETE requests
- Any GET request that changes state (fix these to use proper HTTP methods)
- File uploads
- Settings changes
- Payment/transaction endpoints
**Pre-Authentication Actions:**
- Login endpoints (prevent login CSRF)
- Signup endpoints
- Password reset request endpoints
- Password change endpoints
- Email/phone verification endpoints
- OAuth callback endpoints
#### Protection Mechanisms
1. **CSRF Tokens**
- Generate cryptographically random tokens
- Tie token to user session
- Validate on every state-changing request
- Regenerate after login (prevent session fixation combo)
2. **SameSite Cookies**
```
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
```
- `Strict`: Cookie never sent cross-site (best security)
- `Lax`: Cookie sent on top-level navigations (good balance)
- Always combine with CSRF tokens for defense in depth
3. **Double Submit Cookie Pattern**
- Send CSRF token in both cookie and request body/header
- Server validates they match
#### Edge Cases and Common Mistakes
- **Token presence check**: CSRF validation must NOT depend on whether the token is present, always require it
- **Token per form**: Consider unique tokens per form for sensitive operations
- **JSON APIs**: Don't assume JSON content-type prevents CSRF; validate Origin/Referer headers AND use tokens
- **CORS misconfiguration**: Overly permissive CORS can bypass SameSite cookies
- **Subdomains**: CSRF tokens should be scoped because subdomain takeover can lead to CSRF
- **Flash/PDF uploads**: Legacy browser plugins could bypass SameSite
- **GET requests with side effects**: Never perform state changes on GET
- **Token leakage**: Don't include CSRF tokens in URLs
- **Token in URL vs Header**: Prefer custom headers (X-CSRF-Token) over URL parameters
#### Verification Checklist
- [ ] Token is cryptographically random (use secure random generator)
- [ ] Token is tied to user session
- [ ] Token is validated server-side on all state-changing requests
- [ ] Missing token = rejected request
- [ ] Token regenerated on authentication state change
- [ ] SameSite cookie attribute is set
- [ ] Secure and HttpOnly flags on session cookies
---
### Secret Keys and Sensitive Data Exposure
No secrets or sensitive information should be accessible to client-side code.
#### Never Expose in Client-Side Code
**API Keys and Secrets:**
- Third-party API keys (Stripe, AWS, etc.)
- Database connection strings
- JWT signing secrets
- Encryption keys
- OAuth client secrets
- Internal service URLs/credentials
**Sensitive User Data:**
- Full credit card numbers
- Social Security Numbers
- Passwords (even hashed)
- Security questions/answers
- Full phone numbers (mask them: **_-_**-1234)
- Sensitive PII that isn't needed for display
**Infrastructure Details:**
- Internal IP addresses
- Database schemas
- Debug information
- Stack traces in production
- Server software versions
#### Where Secrets Hide (Check These!)
- JavaScript bundles (including source maps)
- HTML comments
- Hidden form fields
- Data attributes
- LocalStorage/SessionStorage
- Initial state/hydration data in SSR apps
- Environment variables exposed via build tools (NEXT*PUBLIC*_, REACT*APP*_)
#### Best Practices
1. **Environment Variables**: Store secrets in `.env` files
2. **Server-Side Only**: Make API calls requiring secrets from backend only
---
## Open Redirect
Any endpoint accepting a URL for redirection must be protected against open redirect attacks.
### Protection Strategies
1. **Allowlist Validation**
```
allowed_domains = ['yourdomain.com', 'app.yourdomain.com']
function isValidRedirect(url):
parsed = parseUrl(url)
return parsed.hostname in allowed_domains
```
2. **Relative URLs Only**
- Only accept paths (e.g., `/dashboard`) not full URLs
- Validate the path starts with `/` and doesn't contain `//`
3. **Indirect References**
- Use a mapping instead of raw URLs: `?redirect=dashboard` → lookup to `/dashboard`
### Bypass Techniques to Block
| Technique | Example | Why It Works |
|-----------------------|----------------------------------|----------------------------------------------------------|
| @ symbol | `https://legit.com@evil.com` | Browser navigates to evil.com with legit.com as username |
| Subdomain abuse | `https://legit.com.evil.com` | evil.com owns the subdomain |
| Protocol tricks | `javascript:alert(1)` | XSS via redirect |
| Double URL encoding | `%252f%252fevil.com` | Decodes to `//evil.com` after double decode |
| Backslash | `https://legit.com\@evil.com` | Some parsers normalize `\` to `/` |
| Null byte | `https://legit.com%00.evil.com` | Some parsers truncate at null |
| Tab/newline | `https://legit.com%09.evil.com` | Whitespace confusion |
| Unicode normalization | `https://legіt.com` (Cyrillic і) | IDN homograph attack |
| Data URLs | `data:text/html,<script>...` | Direct payload execution |
| Protocol-relative | `//evil.com` | Uses current page's protocol |
| Fragment abuse | `https://legit.com#@evil.com` | Parsed differently by different libraries |
### IDN Homograph Attack Protection
- Convert URLs to Punycode before validation
- Consider blocking non-ASCII domains entirely for sensitive redirects
---
### Password Security
#### Password Requirements
- Minimum 8 characters (12+ recommended)
- No maximum length (or very high, e.g., 128 chars)
- Allow all characters including special chars
- Don't require specific character types (let users choose strong passwords)
#### Storage
- Use Argon2id, bcrypt, or scrypt
- Never MD5, SHA1, or plain SHA256
---
## Server-Side Bugs
### Server-Side Request Forgery (SSRF)
Any functionality where the server makes requests to URLs provided or influenced by users must be protected.
#### Potential Vulnerable Features
- Webhooks (user provides callback URL)
- URL previews
- PDF generators from URLs
- Image/file fetching from URLs
- Import from URL features
- RSS/feed readers
- API integrations with user-provided endpoints
- Proxy functionality
- HTML to PDF/image converters
#### Protection Strategies
1. **Allowlist Approach** (Preferred)
- Only allow requests to pre-approved domains
- Maintain a strict allowlist for integrations
2. **Network Segmentation**
- Run URL-fetching services in isolated network
- Block access to internal network, cloud metadata
#### IP and DNS Bypass Techniques to Block
| Technique | Example | Description |
|----------------------|------------------------------------|-----------------------------------------------------------|
| Decimal IP | `http://2130706433` | 127.0.0.1 as decimal |
| Octal IP | `http://0177.0.0.1` | Octal representation |
| Hex IP | `http://0x7f.0x0.0x0.0x1` | Hexadecimal |
| IPv6 localhost | `http://[::1]` | IPv6 loopback |
| IPv6 mapped IPv4 | `http://[::ffff:127.0.0.1]` | IPv4-mapped IPv6 |
| Short IPv6 | `http://[::]` | All zeros |
| DNS rebinding | Attacker's DNS returns internal IP | First request resolves to external IP, second to internal |
| CNAME to internal | Attacker domain CNAMEs to internal | DNS points to internal hostname |
| URL parser confusion | `http://attacker.com#@internal` | Different parsing behaviors |
| Redirect chains | External URL redirects to internal | Follow redirects carefully |
| IPv6 scope ID | `http://[fe80::1%25eth0]` | Interface-scoped IPv6 |
| Rare IP formats | `http://127.1` | Shortened IP notation |
#### DNS Rebinding Prevention
1. Resolve DNS before making request
2. Validate resolved IP is not internal
3. Pin the resolved IP for the request (don't re-resolve)
4. Or: Resolve twice with delay, ensure both resolve to same external IP
#### Cloud Metadata Protection
Block access to cloud metadata endpoints:
- AWS: `169.254.169.254`
- GCP: `metadata.google.internal`, `169.254.169.254`, `http://metadata`
- Azure: `169.254.169.254`
- DigitalOcean: `169.254.169.254`
#### Implementation Checklist
- [ ] Validate URL scheme is HTTP/HTTPS only
- [ ] Resolve DNS and validate IP is not private/internal
- [ ] Block cloud metadata IPs explicitly
- [ ] Limit or disable redirect following
- [ ] If following redirects, validate each hop
- [ ] Set timeout on requests
- [ ] Limit response size
- [ ] Use network isolation where possible
---
### Insecure File Upload
File uploads must validate type, content, and size to prevent various attacks.
#### Validation Requirements
**1. File Type Validation**
- Check file extension against allowlist
- Validate magic bytes/file signature match expected type
- Never rely on just one check
**2. File Content Validation**
- Read and verify magic bytes
- For images: attempt to process with image library (detects malformed files)
- For documents: scan for macros, embedded objects
- Check for polyglot files (files valid as multiple types)
**3. File Size Limits**
Ver no GitHub