Security
VelinStyle ships sanitization helpers, hardened components, and CSS utilities for user content. The CLI scan command flags XSS, PII, and accessibility issues (see rule table). Use HTTP CSP and Trusted Types for defense in depth.
XSS Protection
Dynamic text should use escapeHTML(), sanitizeURL(), or sanitizeSearchUrl() from components/sanitize.js. Icons use sanitizeSVG() (DOMPurify).
High-signal components
| Component | Notes |
|---|---|
<velin-lightbox> | sanitizeURL + escaped alt |
<velin-search> | Escaped highlights; sanitized href |
<velin-icon> | SVG sanitized; local sprite default |
<velin-email> | Obfuscation only — not encryption |
<velin-secure-field> | Demo only — client encoding, no secrets |
<velin-dialog>, <velin-modal>, … | Escaped titles/messages where dynamic |
How it works
import { escapeHTML, sanitizeURL } from './sanitize.js';
// escapeHTML replaces <, >, &, ", and ' with HTML entities
escapeHTML('<img onerror=alert(1)>');
// → "<img onerror=alert(1)>"
// sanitizeURL blocks javascript: and data: URIs
sanitizeURL('javascript:alert(1)'); // → ""
sanitizeURL('https://example.com'); // → "https://example.com"CSP Compatibility
VelinStyle is designed to work with strict Content-Security-Policy headers. No inline style attributes are injected at runtime, and all Web Components use shadow DOM or class-based styling.
Recommended headers
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self';Trusted Types
VelinStyle exposes getTrustedPolicy() for environments that enforce Trusted Types. Wrap your HTML assignments with the returned policy to stay compliant:
import { getTrustedPolicy } from 'velinstyle';
const policy = getTrustedPolicy();
element.innerHTML = policy.createHTML(safeContent);CSS Security Utilities
The stylesheet src/a11y/security.css provides defensive CSS classes that mitigate common web vulnerabilities without JavaScript.
.velin-user-content
Apply to any container that renders untrusted or user-generated content. It sandboxes the content by blocking scripts, iframes, object embeds, and inline event handlers via CSS containment.
<div class="velin-user-content">
<!-- User-generated HTML goes here -->
<!-- Scripts, iframes, and event handlers are neutralized -->
</div>.velin-secure-frame
Provides clickjacking protection for embedded frames by ensuring the frame cannot be overlaid with invisible elements.
<iframe class="velin-secure-frame" src="..."></iframe>External link indicator
Links with target="_blank" that are missing rel="noopener" automatically receive a visual warning indicator, reminding developers to add the attribute:
/* security.css — flags unsafe external links */
a[target="_blank"]:not([rel~="noopener"])::after {
content: " ⚠";
color: var(--velin-color-warning);
}Autofill & paste-jacking prevention
Password fields are protected against browser autofill abuse, and paste-jacking attacks are mitigated by blocking invisible clipboard-hijacking overlays within .velin-user-content containers.
Form Persistence Security
The velin-persist module stores form data in localStorage for convenience — but it is hardened against abuse:
| Protection | Detail |
|---|---|
| Key validation | Storage keys must be alphanumeric (plus hyphens/underscores), max 64 characters. Invalid keys are rejected silently. |
| Size limit | Each entry is capped at 64 KB. Larger payloads are discarded to prevent localStorage flooding. |
| QuotaExceededError | Caught and handled gracefully — the user is not shown a raw browser error. |
| Sensitive fields excluded | Fields with type="password" and type="file" are never persisted. |
<!-- Password and file fields are automatically excluded -->
<form data-velin-persist="signup">
<input type="text" name="username"> <!-- ✓ persisted -->
<input type="email" name="email"> <!-- ✓ persisted -->
<input type="password" name="password"> <!-- ✗ excluded -->
<input type="file" name="avatar"> <!-- ✗ excluded -->
</form>Security in 0.8.0
sanitize.js—stripControlChars,escapeHTMLAttribute,createSafeHTML- Scanner:
no-meta-refresh,no-inline-style,no-data-html-uri,integrity-missing,postmessage-wildcard --only security|a11y|piifilters onvelinstyle scannpm run test:securityvelinstyle perf auditbasics
Security in 0.9.0
- PII scanner:
--only pii,--fix—velin-email,velin-secure-field velinstyle tokens validate,test:perf,ci:checks- Full rule list: generated/rules/scanner.md
- AAA / WCAG 2.2 CSS modules — Accessibility
PII & email protection (0.9.0)
Three layers: CLI scan, display obfuscation, optional form encoding.
npx velinstyle scan --only pii
npx velinstyle scan --only pii --fix
| Rule | Severity |
|---|---|
pii/hardcoded-email | warning (autofix) |
pii/hardcoded-secret | error |
pii/mailto-in-source | info |
pii/localstorage-pii | warning |
velin-email · velin-secure-field
Security Scanner
VelinStyle ships with a built-in velinstyle scan CLI command that checks your project for common security issues:
npx velinstyle scan
# Example output:
# ✓ All components use escapeHTML()
# ✓ No inline event handlers found
# ⚠ 2 links missing rel="noopener" (src/page.html:14, src/page.html:28)
# ✓ CSP meta tag present
# ✓ No raw innerHTML assignmentsSee the CLI documentation for the full list of scan rules and configuration options.
Best Practices
A quick security checklist for VelinStyle projects:
- Always use
escapeHTML()when injecting user-provided text into the DOM. - Always use
sanitizeURL()before settinghreforsrcfrom untrusted sources. - Set a Content-Security-Policy header — start with the recommended policy above and tighten as needed.
- Add
rel="noopener"to alltarget="_blank"links to prevent tab-nabbing. - Wrap user content in
.velin-user-contentto sandbox it with CSS. - Run
velinstyle scanin CI to catch security regressions automatically. - Keep VelinStyle updated — security patches are noted in the changelog.
- Never persist sensitive data —
velin-persistexcludes passwords by default, but avoid storing tokens or PII in localStorage.