en un clic
security
Write secure web pages and applications. Use when handling user input, forms, external resources, authentication, or implementing security headers and CSP.
Menu
Write secure web pages and applications. Use when handling user input, forms, external resources, authentication, or implementing security headers and CSP.
Generate standardized static site structures. Use when creating new websites, setting up demos, or need consistent site structure with SEO, PWA, and accessibility foundations.
Write Node.js CLI tools with zero dependencies. Use when creating command-line tools, argument parsing, colored output, or interactive prompts.
Modern CSS organization with native @import, @layer cascade control, CSS nesting, design tokens, and element-focused selectors. AUTO-INVOKED when editing .css files.
Define and use custom HTML elements. Use when creating new components, defining custom tags, or using project-specific elements beyond standard HTML5.
Umbrella coordinator for image handling. Coordinates responsive-images, placeholder-images, and automation scripts. Use when adding images to any page, optimizing existing images, or setting up image pipelines.
Generate SVG placeholder images for prototypes. Use when adding placeholder images for layouts, mockups, or development. Supports simple, labeled, and brand-aware types.
| name | security |
| description | Write secure web pages and applications. Use when handling user input, forms, external resources, authentication, or implementing security headers and CSP. |
| allowed-tools | Read, Write, Edit |
This skill ensures web pages and applications follow security best practices to prevent common vulnerabilities.
Key vulnerabilities this skill helps prevent:
| Vulnerability | Prevention |
|---|---|
| Injection (XSS, SQL) | Input validation, output encoding, CSP |
| Broken Authentication | Secure forms, HTTPS, secure cookies |
| Sensitive Data Exposure | HTTPS, secure headers, no secrets in HTML |
| Security Misconfiguration | Proper headers, CSP, secure defaults |
| Cross-Site Scripting (XSS) | CSP, output encoding, input validation |
| Insecure Deserialization | Validate all input, avoid eval() |
| Using Vulnerable Components | SRI for external resources |
| Insufficient Logging | Error handling without exposure |
All production sites MUST use HTTPS:
<head>
<!-- Upgrade insecure requests -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"/>
<!-- Only use HTTPS URLs -->
<link rel="stylesheet" href="https://example.com/styles.css"/>
<script src="https://example.com/script.js"></script>
</head>
Never load HTTP resources on HTTPS pages:
<!-- BAD: Mixed content (blocked by browsers) -->
<img src="http://example.com/image.jpg"/>
<script src="http://cdn.example.com/lib.js"></script>
<!-- GOOD: Always use HTTPS -->
<img src="https://example.com/image.jpg" alt="Description"/>
<script src="https://cdn.example.com/lib.js"></script>
<!-- GOOD: Protocol-relative (inherits page protocol) -->
<img src="//example.com/image.jpg" alt="Description"/>
Document required headers in HTML comments for server configuration:
<!--
Required Security Headers (configure on server):
# Prevent clickjacking
X-Frame-Options: DENY
# Prevent MIME sniffing
X-Content-Type-Options: nosniff
# Enable XSS filter (legacy browsers)
X-XSS-Protection: 1; mode=block
# Control referrer information
Referrer-Policy: strict-origin-when-cross-origin
# Enforce HTTPS
Strict-Transport-Security: max-age=31536000; includeSubDomains
# Permissions policy
Permissions-Policy: geolocation=(), camera=(), microphone=()
-->
Some headers can be set via meta tags:
<head>
<!-- Referrer policy -->
<meta name="referrer" content="strict-origin-when-cross-origin"/>
<!-- Content Security Policy (limited) -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'"/>
<!-- Prevent caching of sensitive pages -->
<meta http-equiv="Cache-Control" content="no-store"/>
<meta http-equiv="Pragma" content="no-cache"/>
</head>
Start with a restrictive policy:
<head>
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
frame-ancestors 'none';
form-action 'self';
base-uri 'self';
"/>
</head>
| Directive | Purpose | Example |
|---|---|---|
default-src | Fallback for all resource types | 'self' |
script-src | JavaScript sources | 'self' https://cdn.example.com |
style-src | CSS sources | 'self' 'unsafe-inline' |
img-src | Image sources | 'self' data: https: |
font-src | Font sources | 'self' https://fonts.gstatic.com |
connect-src | XHR, WebSocket, fetch | 'self' https://api.example.com |
frame-src | iframe sources | 'none' |
frame-ancestors | Who can embed this page | 'none' |
form-action | Form submission targets | 'self' |
base-uri | Restrict <base> element | 'self' |
object-src | Plugins (Flash, etc.) | 'none' |
| Value | Meaning |
|---|---|
'self' | Same origin only |
'none' | Block all |
'unsafe-inline' | Allow inline (avoid if possible) |
'unsafe-eval' | Allow eval() (avoid) |
'nonce-{random}' | Allow specific inline with nonce |
'sha256-{hash}' | Allow specific inline by hash |
https: | Any HTTPS source |
data: | Data URIs |
blob: | Blob URIs |
For inline scripts, use nonces (server must generate unique nonce per request):
<head>
<meta http-equiv="Content-Security-Policy"
content="script-src 'self' 'nonce-abc123random'"/>
</head>
<body>
<!-- Allowed: has matching nonce -->
<script nonce="abc123random">
console.log('This runs');
</script>
<!-- Blocked: no nonce -->
<script>
console.log('This is blocked');
</script>
</body>
For static inline scripts, use hashes:
<head>
<!-- Hash of: console.log('Hello'); -->
<meta http-equiv="Content-Security-Policy"
content="script-src 'self' 'sha256-xyz123...'"/>
</head>
<body>
<script>console.log('Hello');</script>
</body>
Generate hash: echo -n "console.log('Hello');" | openssl sha256 -binary | base64
Always use SRI for third-party resources:
<!-- External CSS with integrity -->
<link rel="stylesheet"
href="https://cdn.example.com/lib.css"
integrity="sha384-abc123..."
crossorigin="anonymous"/>
<!-- External JavaScript with integrity -->
<script src="https://cdn.example.com/lib.js"
integrity="sha384-xyz789..."
crossorigin="anonymous"></script>
# Generate SRI hash for a file
cat file.js | openssl dgst -sha384 -binary | openssl base64 -A
# Or use online tools like srihash.org
| Resource | SRI Required? |
|---|---|
| Third-party CDN scripts | Yes |
| Third-party CDN styles | Yes |
| Self-hosted resources | Optional but recommended |
| Dynamic/frequently updated | Not practical |
Include CSRF tokens in forms:
<form method="post" action="/submit">
<!-- CSRF token (server-generated) -->
<input type="hidden" name="_csrf" value="token-from-server"/>
<!-- Form fields -->
<input type="text" name="username" autocomplete="username"/>
<button type="submit">Submit</button>
</form>
<form method="post"
action="https://example.com/submit"
autocomplete="off">
<!-- Password fields -->
<input type="password"
name="password"
autocomplete="new-password"
minlength="12"
required/>
<!-- Sensitive data -->
<input type="text"
name="ssn"
autocomplete="off"
inputmode="numeric"
pattern="[0-9]{9}"/>
</form>
<!-- GOOD: Explicit HTTPS action -->
<form action="https://example.com/api/submit" method="post">
<!-- BAD: Relative action could be HTTP -->
<form action="/api/submit" method="post">
<!-- BAD: HTTP action -->
<form action="http://example.com/api/submit" method="post">
Use HTML5 validation attributes:
<form-field>
<label for="email">Email</label>
<input type="email"
id="email"
name="email"
required
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
maxlength="254"
autocomplete="email"/>
<output for="email"></output>
</form-field>
<form-field>
<label for="phone">Phone</label>
<input type="tel"
id="phone"
name="phone"
pattern="[0-9]{10}"
inputmode="tel"
autocomplete="tel"/>
<output for="phone"></output>
</form-field>
<form-field>
<label for="url">Website</label>
<input type="url"
id="url"
name="url"
pattern="https://.*"
placeholder="https://example.com"/>
<output for="url"></output>
</form-field>
| Attribute | Purpose |
|---|---|
required | Field must have value |
pattern | Regex pattern to match |
minlength | Minimum character count |
maxlength | Maximum character count |
min / max | Numeric range |
type="email" | Email format validation |
type="url" | URL format validation |
type="tel" | Telephone (no validation, just keyboard) |
<!--
IMPORTANT: Client-side validation is for UX only!
Server MUST:
- Validate all input again
- Sanitize before storage
- Encode before output
- Use parameterized queries
-->
Use the validation skill for comprehensive server-side input validation:
import { validateBody } from './middleware/validate.js';
// Validate request body against JSON Schema
app.post('/api/users',
validateBody('entities/user.create'),
createUser
);
Key security benefits:
additionalProperties: false - Rejects unknown fields (prevents mass assignment)removeAdditional: 'all' - Strips unknown properties before processingSee the validation skill for:
When inserting user data into HTML:
<!-- BAD: Direct insertion (XSS vulnerable) -->
<div id="output"></div>
<script>
document.getElementById('output').innerHTML = userInput; // DANGEROUS!
</script>
<!-- GOOD: Use textContent for text -->
<div id="output"></div>
<script>
document.getElementById('output').textContent = userInput; // Safe
</script>
<!-- GOOD: Use data attributes for values -->
<div data-user-id="123">Content</div>
| Context | Encoding Method |
|---|---|
| HTML body | HTML entity encode (<, >, &) |
| HTML attributes | Attribute encode + quote |
| JavaScript | JavaScript encode or JSON.stringify |
| URL parameters | encodeURIComponent() |
| CSS | CSS encode |
<!-- Add rel="noopener" for security -->
<a href="https://external-site.com"
target="_blank"
rel="noopener noreferrer">
External Link
</a>
rel="noopener"?Without it, the opened page can access window.opener and potentially:
| Attribute | Purpose |
|---|---|
rel="noopener" | Prevent window.opener access |
rel="noreferrer" | Don't send referrer header |
rel="nofollow" | Don't pass SEO value (user content) |
<!-- User-generated content: use all three -->
<a href="https://user-submitted-url.com"
target="_blank"
rel="noopener noreferrer nofollow">
User Link
</a>
<!--
Server header (preferred):
X-Frame-Options: DENY
Or via CSP:
-->
<meta http-equiv="Content-Security-Policy" content="frame-ancestors 'none'"/>
<head>
<style>
/* Hide page if framed (fallback) */
html { display: none; }
</style>
<script>
if (self === top) {
document.documentElement.style.display = 'block';
} else {
top.location = self.location;
}
</script>
</head>
<!-- BAD: API keys in HTML -->
<script>
const API_KEY = 'sk-secret123'; // NEVER DO THIS!
</script>
<!-- BAD: Secrets in data attributes -->
<div data-api-key="sk-secret123">
<!-- GOOD: Use server-side proxy for API calls -->
<form action="/api/proxy" method="post">
<input type="password"
name="password"
autocomplete="current-password"
minlength="12"
aria-describedby="password-requirements"/>
<p id="password-requirements">
Minimum 12 characters with mixed case, numbers, and symbols.
</p>
<!-- Prevent autocomplete on sensitive fields -->
<input type="text"
name="credit-card"
autocomplete="cc-number"
inputmode="numeric"
pattern="[0-9]{16}"/>
<!-- Prevent browser caching of sensitive pages -->
<meta http-equiv="Cache-Control" content="no-store"/>
Document required cookie attributes:
<!--
Cookie Security (set via server):
Set-Cookie: session=abc123;
Secure; # HTTPS only
HttpOnly; # No JavaScript access
SameSite=Strict; # CSRF protection
Path=/; # Scope to root
Max-Age=3600; # 1 hour expiry
Authentication cookies MUST have:
- Secure (HTTPS only)
- HttpOnly (prevent XSS theft)
- SameSite=Strict or Lax (CSRF protection)
-->
| Value | Behavior |
|---|---|
Strict | Cookie only sent for same-site requests |
Lax | Sent for same-site + top-level navigation |
None | Sent for all requests (requires Secure) |
// BAD: innerHTML with user data
element.innerHTML = userInput;
// GOOD: textContent for text
element.textContent = userInput;
// GOOD: createElement for structure
const div = document.createElement('div');
div.textContent = userInput;
parent.appendChild(div);
// GOOD: Template with encoded values
const template = document.getElementById('item-template');
const clone = template.content.cloneNode(true);
clone.querySelector('.name').textContent = userName;
// NEVER USE with user input:
eval(userInput); // Code execution
new Function(userInput); // Code execution
setTimeout(userInput, 1000); // If string, same as eval
setInterval(userInput, 1000); // If string, same as eval
element.innerHTML = userInput; // XSS
document.write(userInput); // XSS
location.href = userInput; // Open redirect
// Validate URLs before use
function isValidHttpUrl(string) {
try {
const url = new URL(string);
return url.protocol === 'https:' || url.protocol === 'http:';
} catch {
return false;
}
}
// Prevent javascript: URLs
function isSafeUrl(string) {
try {
const url = new URL(string, window.location.origin);
return !url.protocol.startsWith('javascript');
} catch {
return false;
}
}
<!-- BAD: Detailed error in HTML -->
<div class="error">
Error: Cannot read property 'id' of undefined
at processUser (app.js:123)
at handleSubmit (app.js:456)
</div>
<!-- GOOD: Generic user-friendly message -->
<div class="error" role="alert">
<p>Something went wrong. Please try again.</p>
<p>If the problem persists, contact support.</p>
</div>
// Log details server-side, show generic message to user
try {
await submitForm(data);
} catch (error) {
// Send to logging service (not console in production)
logError({ error, context: 'form-submit', timestamp: Date.now() });
// Show generic message to user
showError('Unable to submit form. Please try again.');
}
Before deploying:
rel="noopener noreferrer"rel="nofollow" toojavascript: URLs| Skill | Security Overlap |
|---|---|
validation | Server-side input validation with JSON Schema |
forms | Client-side form validation, autocomplete |
javascript-author | Safe DOM manipulation |
metadata | Security meta tags |
performance | Resource loading (SRI compatible) |
| Mistake | Risk | Solution |
|---|---|---|
| HTTP resources | Mixed content blocked | Use HTTPS everywhere |
| No SRI on CDN scripts | Supply chain attack | Add integrity attribute |
| innerHTML with user data | XSS | Use textContent |
| Missing CSRF token | CSRF attacks | Include token in forms |
| target="_blank" without rel | Tab nabbing | Add rel="noopener" |
| Secrets in HTML | Credential theft | Use server-side proxy |
| Detailed error messages | Information disclosure | Generic messages |
| No CSP | XSS easier to exploit | Implement CSP |