Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions packages/core/src/__tests__/middleware/bootstrap-security.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { verifySecurityConfig } from '../../middleware/bootstrap'

describe('verifySecurityConfig', () => {
let warnSpy: ReturnType<typeof vi.spyOn>

beforeEach(() => {
warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
})

afterEach(() => {
warnSpy.mockRestore()
})

it('should not warn when all config is properly set', () => {
verifySecurityConfig({
DB: {} as D1Database,
KV: {} as KVNamespace,
JWT_SECRET: 'a-strong-random-secret-value-here',
CORS_ORIGINS: 'https://mysite.com',
ENVIRONMENT: 'production',
})

expect(warnSpy).not.toHaveBeenCalled()
})

it('should warn when JWT_SECRET is not set', () => {
verifySecurityConfig({
DB: {} as D1Database,
KV: {} as KVNamespace,
CORS_ORIGINS: 'http://localhost:8787',
ENVIRONMENT: 'development',
})

expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('JWT_SECRET is not set')
)
})

it('should warn when JWT_SECRET contains the default value', () => {
verifySecurityConfig({
DB: {} as D1Database,
KV: {} as KVNamespace,
JWT_SECRET: 'your-super-secret-jwt-key-change-in-production',
CORS_ORIGINS: 'http://localhost:8787',
ENVIRONMENT: 'development',
})

expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('JWT_SECRET contains the default value')
)
})

it('should warn when CORS_ORIGINS is not set', () => {
verifySecurityConfig({
DB: {} as D1Database,
KV: {} as KVNamespace,
JWT_SECRET: 'a-strong-secret',
ENVIRONMENT: 'development',
})

expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('CORS_ORIGINS is not set')
)
})

it('should warn when ENVIRONMENT is not set', () => {
verifySecurityConfig({
DB: {} as D1Database,
KV: {} as KVNamespace,
JWT_SECRET: 'a-strong-secret',
CORS_ORIGINS: 'http://localhost:8787',
})

expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('ENVIRONMENT is not set')
)
})

it('should log multiple warnings when multiple items are missing', () => {
verifySecurityConfig({
DB: {} as D1Database,
KV: {} as KVNamespace,
})

expect(warnSpy).toHaveBeenCalledTimes(3)
})

it('should throw in production when JWT_SECRET is not set', () => {
expect(() => {
verifySecurityConfig({
DB: {} as D1Database,
KV: {} as KVNamespace,
CORS_ORIGINS: 'https://mysite.com',
ENVIRONMENT: 'production',
})
}).toThrow('[SonicJS Security] CRITICAL')
})

it('should throw in production when JWT_SECRET is the default value', () => {
expect(() => {
verifySecurityConfig({
DB: {} as D1Database,
KV: {} as KVNamespace,
JWT_SECRET: 'your-super-secret-jwt-key-change-in-production',
CORS_ORIGINS: 'https://mysite.com',
ENVIRONMENT: 'production',
})
}).toThrow('[SonicJS Security] CRITICAL')
})

it('should NOT throw in production when JWT_SECRET is properly set', () => {
verifySecurityConfig({
DB: {} as D1Database,
KV: {} as KVNamespace,
JWT_SECRET: 'a-strong-random-secret-value',
ENVIRONMENT: 'production',
})

// Should warn about CORS_ORIGINS but not throw
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('CORS_ORIGINS is not set')
)
})

it('should NOT throw in development even when JWT_SECRET is missing', () => {
expect(() => {
verifySecurityConfig({
DB: {} as D1Database,
KV: {} as KVNamespace,
ENVIRONMENT: 'development',
})
}).not.toThrow()

// Should still warn
expect(warnSpy).toHaveBeenCalled()
})
})
64 changes: 64 additions & 0 deletions packages/core/src/middleware/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,71 @@ import type { SonicJSConfig } from "../app";
type Bindings = {
DB: D1Database;
KV: KVNamespace;
JWT_SECRET?: string;
CORS_ORIGINS?: string;
ENVIRONMENT?: string;
};

// Track if bootstrap has been run in this worker instance
let bootstrapComplete = false;

/**
* Verify security-critical environment configuration at startup.
* Logs warnings in development, throws in production to prevent
* insecure deployments from silently running.
*/
export function verifySecurityConfig(env: Bindings): void {
const warnings: string[] = [];

// Check JWT secret
if (!env.JWT_SECRET) {
warnings.push(
"JWT_SECRET is not set — using hardcoded fallback. Set via `wrangler secret put JWT_SECRET`"
);
} else if (env.JWT_SECRET.includes("change-in-production")) {
warnings.push(
"JWT_SECRET contains the default value — tokens are forgeable. Generate a strong random secret"
);
}

// Check CORS origins
if (!env.CORS_ORIGINS) {
warnings.push(
"CORS_ORIGINS is not set — all cross-origin API requests will be rejected"
);
}

// Check environment designation
if (!env.ENVIRONMENT) {
warnings.push(
"ENVIRONMENT is not set — HSTS header will not be applied. Set to \"production\" or \"development\""
);
}

if (warnings.length === 0) {
return;
}

const isProduction = env.ENVIRONMENT === "production";

for (const warning of warnings) {
console.warn(`[SonicJS Security] ${warning}`);
}

if (isProduction) {
// In production, a missing or default JWT_SECRET is a hard failure —
// every token issued would be forgeable by anyone reading the source code.
const hasCritical =
!env.JWT_SECRET || env.JWT_SECRET.includes("change-in-production");
if (hasCritical) {
throw new Error(
"[SonicJS Security] CRITICAL: Production deployment is missing a secure JWT_SECRET. " +
"Set it via `wrangler secret put JWT_SECRET` before deploying."
);
}
}
}

/**
* Bootstrap middleware that ensures system initialization
* Runs once per worker instance
Expand Down Expand Up @@ -77,6 +137,10 @@ export function bootstrapMiddleware(config: SonicJSConfig = {}) {
// Don't prevent the app from starting, but log the error
}

// 4. Verify security configuration (outside try/catch so critical
// errors in production propagate and prevent insecure deployments)
verifySecurityConfig(c.env as Bindings);

return next();
};
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

// Bootstrap middleware
export { bootstrapMiddleware } from './bootstrap'
export { bootstrapMiddleware, verifySecurityConfig } from './bootstrap'

// Auth middleware
export { AuthManager, requireAuth, requireRole, optionalAuth } from './auth'
Expand Down