Security
On this page 30
stx provides comprehensive security features to help you build secure applications by default. This page covers all security capabilities and best practices in the stx ecosystem.
Built-in Security Features
Cross-Site Scripting (XSS) Prevention
stx automatically escapes output by default:
<!-- Safe: automatically escaped -->
<div>{{ userInput }}</div>
<!-- Raw HTML: use with caution -->
<div>{{{ trustedHtml }}}</div>
URL schemes in attributes
Escaping stops a value from breaking out of its attribute. It does nothing about
what the attribute means: javascript:alert(1) contains no character an HTML
escape touches, so escaping alone left <a href="{{ url }}"> clickable when the
URL came from a database row or an API.
A value that lands where a URL attribute's scheme is read is checked, and a
scheme that can run script is refused -- prefixed unsafe:, which is an unknown
scheme, so the browser does nothing with it, and the value stays readable to
whoever is debugging it:
<!-- href="unsafe:javascript:alert(1)" -->
<ahref="{{url}}">Profile</a>
<!-- Same for the components that write a URL for you -->
<StxLink:to="user">Website</StxLink>
<StxImage:src="row"alt="" />
<!-- And for a URL bound at runtime -->
<ax-href="">Profile</a>
What is deliberately not checked matters as much:
| Case | Behaviour | Why |
|---|---|---|
title="{{ text }}", <p>{{ text }}</p> | untouched | not a URL |
href="/search?q={{ term }}" | untouched | a scheme can only be at the start of a URL |
{!! url !!} | untouched | the raw form is how a template says it vouches for a value |
src="data:image/..." | still loads | inline images are a normal pattern; only data URLs carrying a document are refused |
Refused schemes: javascript:, vbscript:, livescript:, mocha:,
view-source:, and the data URLs that carry a document (data:text/html,
data:application/xhtml+xml, plus data:image/svg+xml where it would be
navigated to rather than loaded as an image). Spelling does not help an
attacker: whitespace, control characters and case are normalised first, so
java\tscript: and JavaScript: are read as what they are, and an
entity-encoded spelling never becomes a scheme because the HTML escape runs
first.
The rules live in one module, packages/stx/src/url-safety.ts, because four
places need to agree on them: template interpolation, the signals runtime,
<StxLink> and <StxImage>.
CSRF Protection
Built-in CSRF token generation and validation:
// Automatic CSRF protection
app.use(csrf({
cookie: {
httpOnly: true,
secure: true,
sameSite: 'strict'
}
}))
Content Security Policy
Configure CSP headers:
app.use(csp({
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'", "'nonce-{random}'"],
imgSrc: ["'self'", "data:", "https:"]
}
}))
Authentication & Authorization
Authentication Directives
stx provides built-in authentication directives for template-level access control:
<!-- Show content only to authenticated users -->
<p>Welcome back, {{ user.name }}</p>
<!-- Show content only to guests -->
<ahref="">Sign in</a>
<!-- Role-based access -->
@can('')
<ahref="">Admin Panel</a>
Session Security
// Secure session configuration
app.use(session({
secret: process.env.SESSION_SECRET,
cookie: {
secure: true,
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
sameSite: 'strict'
},
rolling: true
}))
Input Validation & Sanitization
Form Validation
import { validate } from '@stacksjs/validation'
const userSchema = {
email: 'email|required',
password: 'string|min:8|required',
name: 'string|max:100|required'
}
app.post('/users', validate(userSchema), (req, res) => {
// Validated data is safe to use
const { email, password, name } = req.validated
})
SQL Injection Prevention
// Parameterized queries (safe)
const user = await db.query(
'SELECT * FROM users WHERE email = ?',
[email]
)
// ORM usage (safe)
const user = await User.where('email', email).first()
File Upload Security
import { upload } from '@stacksjs/upload'
app.post('/upload', upload({
allowedTypes: ['image/jpeg', 'image/png'],
maxSize: 5 * 1024 * 1024, // 5MB
sanitizeFilename: true,
virusScan: true
}))
Security Headers
Comprehensive Header Setup
app.use(helmet({
// XSS Protection
xssFilter: true,
// Content Type Options
noSniff: true,
// Frame Options
frameguard: { action: 'deny' },
// HSTS
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
// Referrer Policy
referrerPolicy: 'strict-origin-when-cross-origin'
}))
Encryption & Hashing
Password Hashing
import { hash, verify } from '@stacksjs/crypto'
// Hash password
const hashedPassword = await hash.password(plainPassword, {
algorithm: 'argon2id',
memoryCost: 65536,
timeCost: 3,
parallelism: 4
})
// Verify password
const isValid = await verify.password(plainPassword, hashedPassword)
Data Encryption
import { encrypt, decrypt } from '@stacksjs/crypto'
// Encrypt sensitive data
const encrypted = await encrypt(sensitiveData, {
algorithm: 'aes-256-gcm',
key: process.env.ENCRYPTION_KEY
})
// Decrypt data
const decrypted = await decrypt(encrypted, {
key: process.env.ENCRYPTION_KEY
})
Rate Limiting
Request Rate Limiting
import { rateLimit } from '@stacksjs/security'
// Global rate limiting
app.use(rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP'
}))
// API-specific rate limiting
app.use('/api', rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: 10 // 10 requests per minute
}))
Brute Force Protection
import { bruteForce } from '@stacksjs/security'
app.post('/login', bruteForce.protect(), async (req, res) => {
// Login logic with automatic brute force protection
})
Security Monitoring
Security Event Logging
import { securityLogger } from '@stacksjs/logging'
// Log security events
securityLogger.warn('Failed login attempt', {
ip: req.ip,
userAgent: req.get('User-Agent'),
email: req.body.email
})
// Log successful authentication
securityLogger.info('User authenticated', {
userId: user.id,
ip: req.ip
})
Intrusion Detection
import { ids } from '@stacksjs/security'
// Configure intrusion detection
app.use(ids({
enabled: true,
rules: [
'detect_sql_injection',
'detect_xss_attempts',
'detect_path_traversal'
],
onDetection: (threat) => {
securityLogger.alert('Security threat detected', threat)
}
}))
Security Testing
Automated Security Scans
# Run security audit
bun audit
# Run SAST (Static Application Security Testing)
bun run security:scan
# Run dependency vulnerability check
bun run security:deps
Penetration Testing
// Security test suite
import { securityTest } from '@stacksjs/testing'
securityTest('XSS protection', () => {
const maliciousInput = '<script>alert("xss")</script>'
const result = render(template, { input: maliciousInput })
expect(result).not.toContain('<script>')
})
securityTest('CSRF protection', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'Test' })
expect(response.status).toBe(403) // CSRF token missing
})
Compliance & Standards
OWASP Top 10 Protection
stx addresses the OWASP Top 10 security risks:
- Injection: Parameterized queries and input validation
- Broken Authentication: Secure session management
- Sensitive Data Exposure: Built-in encryption utilities
- XML External Entities: Safe XML parsing
- Broken Access Control: Role-based authorization
- Security Misconfiguration: Secure defaults
- Cross-Site Scripting: Automatic output escaping
- Insecure Deserialization: Safe serialization practices
- Known Vulnerabilities: Automated dependency scanning
- Insufficient Logging: Comprehensive security logging
Data Protection Compliance
// GDPR compliance helpers
import { gdpr } from '@stacksjs/compliance'
// Data anonymization
const anonymized = gdpr.anonymize(userData)
// Right to be forgotten
await gdpr.deleteUserData(userId)
// Data export
const exportData = gdpr.exportUserData(userId)
Related Resources
- Security Guide - Comprehensive security guide
- Component API - Component authentication patterns
- Deployment Guide - Secure deployment practices
- Testing Guide - Security testing strategies