diff --git a/packages/core/src/app.ts b/packages/core/src/app.ts index 3865b457c..76fc563e3 100644 --- a/packages/core/src/app.ts +++ b/packages/core/src/app.ts @@ -29,6 +29,7 @@ import { import { getCoreVersion } from './utils/version' import { bootstrapMiddleware } from './middleware/bootstrap' import { metricsMiddleware } from './middleware/metrics' +import { securityHeadersMiddleware } from './middleware/security-headers' import { createDatabaseToolsAdminRoutes } from './plugins/core-plugins/database-tools-plugin/admin-routes' import { createSeedDataAdminRoutes } from './plugins/core-plugins/seed-data-plugin/admin-routes' import { emailPlugin } from './plugins/core-plugins/email-plugin' @@ -164,10 +165,7 @@ export function createSonicJSApp(config: SonicJSConfig = {}): SonicJSApp { }) // Security middleware - app.use('*', async (_c, next) => { - // Security headers, CORS, etc. - await next() - }) + app.use('*', securityHeadersMiddleware()) // Custom middleware - after auth if (config.middleware?.afterAuth) { diff --git a/packages/core/src/middleware/index.ts b/packages/core/src/middleware/index.ts index 96abeb470..d86d4056a 100644 --- a/packages/core/src/middleware/index.ts +++ b/packages/core/src/middleware/index.ts @@ -31,7 +31,7 @@ export const securityLoggingMiddleware: any = () => async (_c: any, next: any) = export const performanceLoggingMiddleware: any = () => async (_c: any, next: any) => await next() export const cacheHeaders: any = () => async (_c: any, next: any) => await next() export const compressionMiddleware: any = async (_c: any, next: any) => await next() -export const securityHeaders: any = () => async (_c: any, next: any) => await next() +export { securityHeadersMiddleware as securityHeaders } from './security-headers' // Other stubs export const PermissionManager: any = {} diff --git a/packages/core/src/middleware/security-headers.ts b/packages/core/src/middleware/security-headers.ts new file mode 100644 index 000000000..ef94b797e --- /dev/null +++ b/packages/core/src/middleware/security-headers.ts @@ -0,0 +1,23 @@ +import { Context, Next } from 'hono' + +/** + * Security headers middleware. + * Sets standard security headers on every response. + * Skips HSTS in development to avoid local dev issues. + */ +export const securityHeadersMiddleware = () => { + return async (c: Context, next: Next) => { + await next() + + c.header('X-Content-Type-Options', 'nosniff') + c.header('X-Frame-Options', 'SAMEORIGIN') + c.header('Referrer-Policy', 'strict-origin-when-cross-origin') + c.header('Permissions-Policy', 'camera=(), microphone=(), geolocation=()') + + // Only set HSTS in non-development environments + const environment = (c.env as any)?.ENVIRONMENT + if (environment !== 'development') { + c.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains') + } + } +}