with one click
vibe-security-skill
// This skill helps Claude write secure web applications. Use when working on any web application to ensure security best practices are followed.
// This skill helps Claude write secure web applications. Use when working on any web application to ensure security best practices are followed.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | Vibe Security Skill |
| description | 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://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 |
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.