@ordojs/security
Version:
Security package for OrdoJS with XSS, CSRF, and injection protection
170 lines • 5.29 kB
JavaScript
/**
* Content Security Policy (CSP) manager for XSS protection
*/
/**
* Default secure CSP policy
*/
const DEFAULT_CSP_POLICY = {
'default-src': ["'self'"],
'script-src': ["'self'"],
'style-src': ["'self'", "'unsafe-inline'"], // Allow inline styles for component styling
'img-src': ["'self'", 'data:', 'https:'],
'font-src': ["'self'", 'https:'],
'connect-src': ["'self'"],
'media-src': ["'self'"],
'object-src': ["'none'"],
'frame-src': ["'none'"],
'base-uri': ["'self'"],
'form-action': ["'self'"],
'frame-ancestors': ["'none'"],
'upgrade-insecure-requests': [],
};
/**
* Content Security Policy manager for XSS protection
*/
export class CspManager {
policy;
options;
nonces = new Set();
/**
* Create a new CSP manager
* @param policy Initial CSP policy
* @param options CSP manager options
*/
constructor(policy = {}, options = {}) {
this.policy = { ...DEFAULT_CSP_POLICY };
// Merge user policy, ensuring all values are arrays
Object.entries(policy).forEach(([key, value]) => {
if (value) {
this.policy[key] = value;
}
});
this.options = {
reportViolations: false,
reportOnly: false,
...options,
};
}
/**
* Generate a cryptographically secure nonce for inline scripts/styles
* @returns Base64 encoded nonce
*/
generateNonce() {
const array = new Uint8Array(16);
crypto.getRandomValues(array);
const nonce = btoa(String.fromCharCode(...array));
this.nonces.add(nonce);
return nonce;
}
/**
* Add a nonce to the script-src directive
* @param nonce The nonce to add
*/
addScriptNonce(nonce) {
this.addSourceToDirective('script-src', `'nonce-${nonce}'`);
}
/**
* Add a nonce to the style-src directive
* @param nonce The nonce to add
*/
addStyleNonce(nonce) {
this.addSourceToDirective('style-src', `'nonce-${nonce}'`);
}
/**
* Add a hash to a directive for inline content
* @param directive The CSP directive
* @param hash The SHA hash (e.g., 'sha256-abc123...')
*/
addHash(directive, hash) {
this.addSourceToDirective(directive, `'${hash}'`);
}
/**
* Add a source to a CSP directive
* @param directive The CSP directive
* @param source The source to add
*/
addSourceToDirective(directive, source) {
if (!this.policy[directive]) {
this.policy[directive] = [];
}
if (!this.policy[directive].includes(source)) {
this.policy[directive].push(source);
}
}
/**
* Remove a source from a CSP directive
* @param directive The CSP directive
* @param source The source to remove
*/
removeSourceFromDirective(directive, source) {
if (this.policy[directive]) {
this.policy[directive] = this.policy[directive].filter(s => s !== source);
}
}
/**
* Generate the CSP header value
* @returns CSP header value string
*/
generateHeader() {
const directives = Object.entries(this.policy)
.filter(([_, sources]) => sources.length > 0)
.map(([directive, sources]) => {
if (sources.length === 0) {
return directive;
}
return `${directive} ${sources.join(' ')}`;
});
if (this.options.reportUri) {
directives.push(`report-uri ${this.options.reportUri}`);
}
return directives.join('; ');
}
/**
* Get the appropriate CSP header name
* @returns CSP header name
*/
getHeaderName() {
return this.options.reportOnly
? 'Content-Security-Policy-Report-Only'
: 'Content-Security-Policy';
}
/**
* Create a strict CSP policy for maximum security
* @returns New CspManager with strict policy
*/
static createStrict() {
return new CspManager({
'default-src': ["'none'"],
'script-src': ["'self'"],
'style-src': ["'self'"],
'img-src': ["'self'"],
'font-src': ["'self'"],
'connect-src': ["'self'"],
'media-src': ["'none'"],
'object-src': ["'none'"],
'frame-src': ["'none'"],
'base-uri': ["'none'"],
'form-action': ["'self'"],
'frame-ancestors': ["'none'"],
});
}
/**
* Create a development-friendly CSP policy
* @returns New CspManager with development policy
*/
static createDevelopment() {
return new CspManager({
'default-src': ["'self'"],
'script-src': ["'self'", "'unsafe-eval'"], // Allow eval for dev tools
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:', 'http:'],
'font-src': ["'self'", 'https:', 'data:'],
'connect-src': ["'self'", 'ws:', 'wss:'], // Allow WebSocket for HMR
});
}
}
/**
* Default CSP manager instance
*/
export const defaultCspManager = new CspManager();
//# sourceMappingURL=csp-manager.js.map