From bf9dbd44fbbfc70f0c2e98513d31062d994fe82c Mon Sep 17 00:00:00 2001 From: Lucas Coratger <73360179+coratgerl@users.noreply.github.com> Date: Tue, 17 Feb 2026 18:17:24 +0100 Subject: [PATCH] feat(wabe): protect start with empty root key --- lefthook.yml | 2 +- packages/wabe/src/security.test.ts | 31 ++++++++++++++++++++++++++- packages/wabe/src/server/index.ts | 3 +++ packages/wabe/src/utils/testHelper.ts | 3 ++- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index 38b60e61..c35659c5 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -3,4 +3,4 @@ pre-commit: commands: check: glob: '*.{js,ts,jsx,tsx}' - run: oxlint {staged_files} && bun format && git add {staged_files} + run: bun oxlint {staged_files} && bun format && git add {staged_files} diff --git a/packages/wabe/src/security.test.ts b/packages/wabe/src/security.test.ts index 4b89263a..d261fdb2 100644 --- a/packages/wabe/src/security.test.ts +++ b/packages/wabe/src/security.test.ts @@ -1,4 +1,6 @@ import { describe, it, expect } from 'bun:test' +import { v4 as uuid } from 'uuid' +import getPort from 'get-port' import { decode, sign } from 'jsonwebtoken' import { gql } from 'graphql-request' import { @@ -8,11 +10,38 @@ import { createUserAndUpdateRole, getUserClient, } from './utils/helper' -import { setupTests, closeTests } from './utils/testHelper' +import { setupTests, closeTests, getDatabaseAdapter } from './utils/testHelper' import { RoleEnum } from 'generated/wabe' import { Session } from './authentication/Session' +import { Wabe } from './server' +import type { DevWabeTypes } from './utils/helper' describe('Security tests', () => { + it('should throw at server startup when rootKey is empty', async () => { + const databaseId = uuid() + const port = await getPort() + + const wabe = new Wabe({ + isProduction: false, + rootKey: '', + database: { + // @ts-expect-error + adapter: await getDatabaseAdapter(databaseId), + }, + port, + schema: { + classes: [ + { + name: 'Collection1', + fields: { name: { type: 'String' } }, + }, + ], + }, + }) + + await expect(wabe.start()).rejects.toThrow('rootKey cannot be empty') + }) + it('should not return private fields (acl) on createObject, getObject and getObjects if not root', async () => { const setup = await setupTests([ { diff --git a/packages/wabe/src/server/index.ts b/packages/wabe/src/server/index.ts index 79ee2242..c7f718ce 100644 --- a/packages/wabe/src/server/index.ts +++ b/packages/wabe/src/server/index.ts @@ -209,6 +209,9 @@ export class Wabe { } async start() { + if (!this.config.rootKey || this.config.rootKey.length === 0) + throw new Error('rootKey cannot be empty') + if (this.config.authentication?.session && !this.config.authentication.session.jwtSecret) throw new Error('Authentication session requires jwt secret') diff --git a/packages/wabe/src/utils/testHelper.ts b/packages/wabe/src/utils/testHelper.ts index 0654b2ec..51f69a7b 100644 --- a/packages/wabe/src/utils/testHelper.ts +++ b/packages/wabe/src/utils/testHelper.ts @@ -20,6 +20,7 @@ export const setupTests = async ( options: { isProduction?: boolean disableCSRFProtection?: boolean + rootKey?: string } = {}, ) => { const databaseId = uuid() @@ -28,7 +29,7 @@ export const setupTests = async ( const wabe = new Wabe({ isProduction: !!options.isProduction, - rootKey: 'dev', + rootKey: options.rootKey ?? 'dev', database: { // @ts-expect-error adapter: await getDatabaseAdapter(databaseId),