This skill helps Claude write secure web applications. Use when working on any web application to ensure security best practices are followed.
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:
Access control vulnerabilities occur when users can access resources or perform actions beyond their intended permissions.
For every data point and action that requires authentication:
User-Level Authorization
Use UUIDs Instead of Sequential IDs
Account Lifecycle Handling
# 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
Every input controllable by the user—whether directly or indirectly—must be sanitized against XSS.
Direct Inputs:
Indirect Inputs:
Often Overlooked:
Output Encoding (Context-Specific)
< → <)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';
'unsafe-inline' and 'unsafe-eval' for scriptsreport-uri /csp-reportInput Sanitization
Additional Headers
X-Content-Type-Options: nosniffX-Frame-Options: DENY (or use CSP frame-ancestors)Every state-changing endpoint must be protected against CSRF attacks.
Authenticated Actions:
Pre-Authentication Actions:
CSRF Tokens
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)Double Submit Cookie Pattern
No secrets or sensitive information should be accessible to client-side code.
API Keys and Secrets:
Sensitive User Data:
Infrastructure Details:
.env filesAny endpoint accepting a URL for redirection must be protected against open redirect attacks.
Allowlist Validation
allowed_domains = ['yourdomain.com', 'app.yourdomain.com']
function isValidRedirect(url):
parsed = parseUrl(url)
return parsed.hostname in allowed_domains
Relative URLs Only
/dashboard) not full URLs/ and doesn't contain //Indirect References
?redirect=dashboard → lookup to /dashboard| Technique | Example | Why It Works |
|---|---|---|
| @ symbol | https://[email protected] | 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 |
Any functionality where the server makes requests to URLs provided or influenced by users must be protected.
Allowlist Approach (Preferred)
Network Segmentation
| 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 |
Block access to cloud metadata endpoints:
169.254.169.254metadata.google.internal, 169.254.169.254, http://metadata169.254.169.254169.254.169.254File uploads must validate type, content, and size to prevent various attacks.
1. File Type Validation
2. File Content Validation
3. File Size Limits
| Attack | Description | Prevention |
|---|---|---|
| Extension bypass | shell.php.jpg | Check full extension, use allowlist |
| Null byte | shell.php%00.jpg | Sanitize filename, check for null bytes |
| Double extension | shell.jpg.php | Only allow single extension |
| MIME type spoofing | Set Content-Type to image/jpeg | Validate magic bytes |
| Magic byte injection | Prepend valid magic bytes to malicious file | Check entire file structure, not just header |
| Polyglot files | File valid as both JPEG and JavaScript | Parse file as expected type, reject if invalid |
| SVG with JavaScript | <svg onload="alert(1)"> | Sanitize SVG or disallow entirely |
| XXE via file upload | Malicious DOCX, XLSX (which are XML) | Disable external entities in parser |
| ZIP slip | ../../../etc/passwd in archive | Validate extracted paths |
| ImageMagick exploits | Specially crafted images | Keep ImageMagick updated, use policy.xml |
| Filename injection | ; rm -rf / in filename | Sanitize filenames, use random names |
| Content-type confusion | Browser MIME sniffing | Set X-Content-Type-Options: nosniff |
| Type | Magic Bytes (hex) |
|---|---|
| JPEG | FF D8 FF |
| PNG | 89 50 4E 47 0D 0A 1A 0A |
| GIF | 47 49 46 38 |
25 50 44 46 | |
| ZIP | 50 4B 03 04 |
| DOCX/XLSX | 50 4B 03 04 (ZIP-based) |
Content-Disposition: attachment (forces download)X-Content-Type-Options: nosniffContent-Type matching actual file typeSQL injection occurs when user input is incorporated into SQL queries without proper handling.
1. Parameterized Queries (Prepared Statements) — PRIMARY DEFENSE
-- VULNERABLE
query = "SELECT * FROM users WHERE id = " + userId
-- SECURE
query = "SELECT * FROM users WHERE id = ?"
execute(query, [userId])
2. ORM Usage
3. Input Validation
xp_cmdshell in SQL ServerXXE vulnerabilities occur when XML parsers process external entity references in user-supplied XML.
Direct XML Input:
Indirect XML:
Java:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setExpandEntityReferences(false);
Python (lxml):
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True)
# Or use defusedxml library
PHP:
libxml_disable_entity_loader(true);
// Or use XMLReader with proper settings
Node.js:
// Use libraries that disable DTD processing by default
// If using libxmljs, set { noent: false, dtdload: false }
.NET:
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.XmlResolver = null;
Path traversal vulnerabilities occur when user input controls file paths, allowing access to files outside intended directories.
# VULNERABLE
file_path = "/uploads/" + user_input
file_path = base_dir + request.params['file']
template = "templates/" + user_provided_template
1. Avoid User Input in Paths
# Instead of using user input directly
# Use indirect references
files = {'report': '/reports/q1.pdf', 'invoice': '/invoices/2024.pdf'}
file_path = files.get(user_input) # Returns None if invalid
2. Canonicalization and Validation
import os
def safe_join(base_directory, user_path):
# Ensure base is absolute and normalized
base = os.path.abspath(os.path.realpath(base_directory))
# Join and then resolve the result
target = os.path.abspath(os.path.realpath(os.path.join(base, user_path)))
# Ensure the commonpath is the base directory
if os.path.commonpath([base, target]) != base:
raise ValueError("Error!")
return target
3. Input Sanitization
.. sequences/, C:)Include these headers in all responses:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Security-Policy: [see XSS section]
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Cache-Control: no-store (for sensitive pages)
When generating code, always:
When unsure, choose the more restrictive/secure option and document the security consideration in comments.