farrow-helmet
Version:
Security middleware for Farrow HTTP applications that helps secure your web apps by setting various HTTP security headers
711 lines (560 loc) • 17 kB
Markdown
# farrow-helmet
[](https://www.npmjs.com/package/farrow-helmet)
[](https://www.typescriptlang.org/)
[](https://opensource.org/licenses/MIT)
[](./src/index.test.ts)
A security middleware for [Farrow HTTP](https://github.com/farrow-js/farrow) applications that helps secure your web apps by setting various HTTP security headers.
**farrow-helmet** is a TypeScript-first security middleware designed specifically for the Farrow HTTP framework, providing comprehensive protection against common web vulnerabilities.
[中文文档](./README_ZH.md) | English
## Table of Contents
- [Features](#features)
- [Quick Start](#quick-start)
- [Installation](#installation)
- [Basic Usage](#basic-usage)
- [Security Headers](#security-headers)
- [Configuration](#configuration)
- [Advanced Usage](#advanced-usage)
- [Best Practices](#best-practices)
- [Examples](#examples)
- [API Reference](#api-reference)
- [Testing](#testing)
- [Contributing](#contributing)
- [License](#license)
## Features
🛡️ **Comprehensive Security Headers** - Automatically sets essential security headers
🔧 **Highly Configurable** - Fine-tune security policies for your needs
⚡ **TypeScript First** - Full type safety and IntelliSense support
🚀 **Minimal Dependencies** - Lightweight wrapper around proven helmet.js
🧪 **Well Tested** - 21+ test cases covering all functionality
📦 **Farrow Optimized** - Designed specifically for Farrow HTTP framework
## Quick Start
```bash
npm install farrow-helmet
```
```typescript
import { Http, Response } from 'farrow-http'
import { helmet } from 'farrow-helmet'
const app = Http()
// Apply security middleware
app.use(helmet())
app.get('/').use(() => Response.json({
message: '🛡️ Secured with farrow-helmet!'
}))
app.listen(3000)
```
That's it! Your Farrow application is now protected with essential security headers.
## Installation
```bash
# npm
npm install farrow-helmet
# yarn
yarn add farrow-helmet
# pnpm
pnpm add farrow-helmet
```
**Prerequisites:**
- Node.js 16+
- TypeScript 4.5+
- farrow-http 2.x
## Basic Usage
### Default Protection
Apply helmet with sensible defaults:
```typescript
import { helmet } from 'farrow-helmet'
app.use(helmet())
```
This automatically enables:
- Content type sniffing protection
- Clickjacking protection
- XSS filtering (modern approach)
- Referrer policy controls
- Download security for IE
- Cross-domain policy restrictions
### Custom Configuration
Customize security policies to fit your needs:
```typescript
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
},
hsts: {
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true
}
}))
```
## Security Headers
farrow-helmet sets the following security headers by default:
| Header | Default Value | Purpose |
|--------|---------------|---------|
| `X-Content-Type-Options` | `nosniff` | Prevents MIME type sniffing attacks |
| `X-Frame-Options` | `SAMEORIGIN` | Prevents clickjacking attacks |
| `X-XSS-Protection` | `0` | Modern XSS protection (disables legacy filter) |
| `Referrer-Policy` | `no-referrer` | Controls referrer information leakage |
| `X-Download-Options` | `noopen` | Prevents IE from executing downloads |
| `X-Permitted-Cross-Domain-Policies` | `none` | Restricts Flash/PDF cross-domain access |
### Additional Available Headers
Configure these headers with custom options:
- **Content Security Policy (CSP)** - Prevents XSS and injection attacks
- **HTTP Strict Transport Security (HSTS)** - Enforces HTTPS connections
- **X-DNS-Prefetch-Control** - Controls DNS prefetching
- **Expect-CT** - Certificate transparency enforcement
- **Feature-Policy / Permissions-Policy** - Controls browser features
## Configuration
### Content Security Policy (CSP)
Protect against XSS and injection attacks:
```typescript
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://trusted-cdn.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
fontSrc: ["'self'", "https:", "data:"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
frameAncestors: ["'none'"],
blockAllMixedContent: [],
upgradeInsecureRequests: []
}
}
}))
```
### HTTP Strict Transport Security (HSTS)
Force HTTPS connections:
```typescript
app.use(helmet({
hsts: {
maxAge: 31536000, // 1 year in seconds
includeSubDomains: true, // Apply to all subdomains
preload: true // Enable HSTS preloading
}
}))
```
### Environment-Specific Configuration
Configure different policies for different environments:
```typescript
const isDevelopment = process.env.NODE_ENV === 'development'
const isProduction = process.env.NODE_ENV === 'production'
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
"'self'",
...(isDevelopment ? ["'unsafe-eval'", "'unsafe-inline'"] : [])
],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "blob:"]
}
},
hsts: isProduction ? {
maxAge: 63072000, // 2 years
includeSubDomains: true,
preload: true
} : false, // Disable HSTS in development
// Report CSP violations in development
contentSecurityPolicyReportOnly: isDevelopment
}))
```
### Disabling Specific Headers
Disable headers you don't need:
```typescript
app.use(helmet({
xFrameOptions: false, // Disable X-Frame-Options
contentSecurityPolicy: false, // Disable CSP
xContentTypeOptions: false // Disable X-Content-Type-Options
}))
```
## Advanced Usage
### Integration with Other Middleware
Proper middleware ordering is crucial for security:
```typescript
const app = Http({ logger: true })
// 1. Error handling (first)
app.use(async (req, next) => {
try {
return await next(req)
} catch (error) {
console.error('Request failed:', error)
return Response.status(500).json({
error: 'Internal server error'
})
}
})
// 2. Security headers (early)
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"]
}
}
}))
// 3. CORS (after security)
import { cors } from 'farrow-cors'
app.use(cors({
origin: 'https://trusted-domain.com',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}))
// 4. Authentication
app.use(authenticationMiddleware)
// 5. Business logic
app.get('/api/data').use(() => Response.json({ data: [] }))
```
### Router-Specific Security
Apply different security policies to different routes:
```typescript
import { Router } from 'farrow-http'
// Public API - Restrictive CSP
const publicRouter = Router()
publicRouter.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'none'"],
scriptSrc: ["'none'"],
styleSrc: ["'none'"],
imgSrc: ["'none'"]
}
}
}))
publicRouter.get('/status').use(() => Response.json({ status: 'ok' }))
// Admin panel - More permissive
const adminRouter = Router()
adminRouter.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
}
}))
adminRouter.get('/dashboard').use(() => Response.html(dashboardHTML))
// Mount routers
app.route('/api').use(publicRouter)
app.route('/admin').use(adminRouter)
```
### Dynamic Security Policies
Adjust policies based on request context:
```typescript
const dynamicHelmet = (req: RequestInfo, next: any) => {
const isAPIRequest = req.pathname.startsWith('/api')
const isAdminRequest = req.pathname.startsWith('/admin')
let helmetConfig = {}
if (isAPIRequest) {
helmetConfig = {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'none'"]
}
}
}
} else if (isAdminRequest) {
helmetConfig = {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"]
}
}
}
}
return helmet(helmetConfig)(req, next)
}
app.use(dynamicHelmet)
```
## Best Practices
### 1. Start with Strict Policies
Begin with restrictive policies and gradually relax them:
```typescript
// Start strict
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'none'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'"],
imgSrc: ["'self'"]
}
}
}))
// Then add exceptions as needed
// scriptSrc: ["'self'", "https://trusted-cdn.com"]
```
### 2. Use CSP Report-Only Mode for Testing
Test CSP policies without breaking functionality:
```typescript
app.use(helmet({
contentSecurityPolicyReportOnly: {
directives: {
defaultSrc: ["'self'"],
reportUri: ['/csp-report']
}
}
}))
// Handle CSP reports
app.post('/csp-report').use((req) => {
console.log('CSP Violation:', req.body)
return Response.status(204).empty()
})
```
### 3. Environment-Specific Security
```typescript
const securityConfig = {
development: {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-eval'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
},
hsts: false
},
production: {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}
}
app.use(helmet(securityConfig[process.env.NODE_ENV] || securityConfig.production))
```
### 4. Monitor and Log Security Headers
```typescript
app.use(helmet())
app.use((req, next) => {
const response = next(req)
// Log security-relevant requests
if (req.pathname.includes('..') || req.pathname.includes('<script>')) {
console.warn('Suspicious request:', {
ip: req.headers['x-forwarded-for'] || 'unknown',
userAgent: req.headers['user-agent'],
path: req.pathname
})
}
return response
})
```
## Examples
### Complete Production Setup
```typescript
import { Http, Response, Router } from 'farrow-http'
import { helmet } from 'farrow-helmet'
import { createContext } from 'farrow-pipeline'
const app = Http({
logger: {
ignoreIntrospectionRequestOfFarrowApi: true
}
})
// Security configuration
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
baseUri: ["'self'"],
blockAllMixedContent: [],
fontSrc: ["'self'", "https:", "data:"],
frameAncestors: ["'self'"],
imgSrc: ["'self'", "data:"],
objectSrc: ["'none'"],
scriptSrc: ["'self'"],
scriptSrcAttr: ["'none'"],
styleSrc: ["'self'", "https:", "'unsafe-inline'"],
upgradeInsecureRequests: []
}
},
hsts: {
maxAge: 63072000, // 2 years
includeSubDomains: true,
preload: true
}
}))
// CORS for API
import { cors } from 'farrow-cors'
const apiRouter = Router()
apiRouter.use(cors({
origin: 'https://yourdomain.com',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Total-Count', 'X-Request-ID']
}))
apiRouter.get('/users').use(() => {
return Response.json({ users: [] })
})
app.route('/api').use(apiRouter)
// Static files with security
app.serve('/static', './public')
// Health check
app.get('/health').use(() => {
return Response.json({
status: 'healthy',
timestamp: new Date().toISOString()
})
})
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`🛡️ Secure server running on http://localhost:${port}`)
})
```
### CSP Violation Reporting
```typescript
import { ObjectType, String, Optional, Any } from 'farrow-schema'
class CSPReport extends ObjectType {
documentUri = String
referrer = Optional(String)
violatedDirective = String
originalPolicy = String
blockedUri = Optional(String)
sourceFile = Optional(String)
lineNumber = Optional(Number)
columnNumber = Optional(Number)
}
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
reportUri: ['/csp-report']
}
}
}))
app.post('/csp-report', { body: { "csp-report": CSPReport } }).use((req) => {
const report = req.body["csp-report"]
console.warn('CSP Violation:', {
uri: report.documentUri,
directive: report.violatedDirective,
blocked: report.blockedUri,
source: report.sourceFile,
line: report.lineNumber,
timestamp: new Date().toISOString()
})
// Store in database or send to monitoring service
// await logCSPViolation(report)
return Response.status(204).empty()
})
```
## API Reference
### `helmet(options?: HelmetOptions): HttpMiddleware`
The main helmet function that returns a Farrow HTTP middleware.
#### HelmetOptions
All options are optional and match the [helmet.js](https://helmetjs.github.io/) API:
```typescript
interface HelmetOptions {
contentSecurityPolicy?: CSPOptions | false
hsts?: HSTSOptions | false
xContentTypeOptions?: boolean
xDnsPrefetchControl?: { allow?: boolean } | false
xDownloadOptions?: boolean
xFrameOptions?: XFrameOptionsOptions | false
xPermittedCrossDomainPolicies?: XPermittedCrossDomainPoliciesOptions | false
xPoweredBy?: boolean
xXssProtection?: boolean
referrerPolicy?: ReferrerPolicyOptions | false
// ... and more
}
```
#### CSPOptions
Content Security Policy configuration:
```typescript
interface CSPOptions {
directives?: {
defaultSrc?: string[]
scriptSrc?: string[]
styleSrc?: string[]
imgSrc?: string[]
fontSrc?: string[]
objectSrc?: string[]
baseUri?: string[]
formAction?: string[]
frameAncestors?: string[]
// ... and more
}
reportOnly?: boolean
reportUri?: string[]
}
```
#### HSTSOptions
HTTP Strict Transport Security configuration:
```typescript
interface HSTSOptions {
maxAge?: number // Max age in seconds
includeSubDomains?: boolean
preload?: boolean
}
```
For complete API documentation, see the [helmet.js documentation](https://helmetjs.github.io/).
## Testing
farrow-helmet comes with comprehensive tests covering all functionality:
```bash
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverage
```
### Testing Your Security Setup
Test your security headers:
```typescript
import { Http } from 'farrow-http'
import { helmet } from 'farrow-helmet'
import request from 'supertest'
const app = Http()
app.use(helmet())
app.get('/test').use(() => Response.json({ message: 'test' }))
describe('Security Headers', () => {
it('should set security headers', async () => {
const response = await request(app.server())
.get('/test')
.expect(200)
expect(response.headers['x-content-type-options']).toBe('nosniff')
expect(response.headers['x-frame-options']).toBe('SAMEORIGIN')
expect(response.headers['x-xss-protection']).toBe('0')
expect(response.headers['referrer-policy']).toBe('no-referrer')
})
})
```
## Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
### Development Setup
```bash
# Clone the repository
git clone https://github.com/AisonSu/farrow-helmet.git
cd farrow-helmet
# Install dependencies
pnpm install
# Run tests
pnpm test
# Build the project
pnpm build
```
### Reporting Issues
Please report security issues privately to [aisonsu@outlook.com](mailto:aisonsu@outlook.com).
For other issues, please use the [GitHub issue tracker](https://github.com/AisonSu/farrow-helmet/issues).
## License
MIT © [Aison](https://github.com/AisonSu)
## Related
- [Farrow](https://github.com/farrow-js/farrow) - The Farrow framework
- [helmet.js](https://helmetjs.github.io/) - The original helmet library for Express
- [OWASP Secure Headers](https://owasp.org/www-project-secure-headers/) - Security header guidelines
**Security Notice**: While farrow-helmet helps secure your applications by setting security headers, it's not a silver bullet. Always follow security best practices, keep your dependencies updated, and consider additional security measures like input validation, authentication, and authorization.