Security
VelinStyle takes security seriously — XSS protection is built into every Web Component, CSS utilities sandbox user content, and the CLI scan command flags common XSS and accessibility issues in your own HTML/JS/CSS (see rule table). Use HTTP CSP and Trusted Types for defense in depth.
XSS Protection
All nine Web Components are hardened against cross-site scripting. Every component that renders dynamic content passes values through escapeHTML() and sanitizeURL() from components/sanitize.js.
Protected components
| Component | Sanitization |
|---|---|
<velin-toast> | escapeHTML() on message content |
<velin-dialog> | escapeHTML() on title & body |
<velin-lightbox> | sanitizeURL() on image sources, escapeHTML() on captions |
<velin-countdown> | escapeHTML() on label attributes |
<velin-stepper-wc> | escapeHTML() on step labels |
<velin-tooltip-wc> | escapeHTML() on tooltip text |
<velin-popover> | escapeHTML() on title & content |
<velin-drawer> | escapeHTML() on heading |
<velin-modal> | escapeHTML() on title, sanitizeURL() on action links |
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 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.