From 7f4caae7b3a6194df69510da29bb3028510491af Mon Sep 17 00:00:00 2001 From: ABHAY PANDEY Date: Sun, 11 Oct 2026 08:10:05 +0530 Subject: [PATCH 1/2] fix(settings): reject rateLimits with non-positive period --- .changeset/validate-rate-limit-period.md | 5 +++++ src/utils/settings-config.ts | 27 ++++++++++++++++++++++++ test/unit/utils/settings-config.spec.ts | 24 +++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 .changeset/validate-rate-limit-period.md diff --git a/.changeset/validate-rate-limit-period.md b/.changeset/validate-rate-limit-period.md new file mode 100644 index 000000000..d8e7d524b --- /dev/null +++ b/.changeset/validate-rate-limit-period.md @@ -0,0 +1,5 @@ +--- +"nostream": patch +--- + +Reject settings where any rateLimits[].period is not greater than 0 diff --git a/src/utils/settings-config.ts b/src/utils/settings-config.ts index f3937b6a7..e218bef7f 100644 --- a/src/utils/settings-config.ts +++ b/src/utils/settings-config.ts @@ -570,6 +570,21 @@ export const validatePathAgainstDefaults = (path: string): ValidationIssue[] => ] } +const validateRateLimitPeriods = ( + rateLimits: { period: number }[] | undefined, + pathPrefix: string, + issues: ValidationIssue[], +): void => { + if (!Array.isArray(rateLimits)) { + return + } + rateLimits.forEach((entry, index) => { + if (!(entry.period > 0)) { + issues.push({ path: `${pathPrefix}[${index}].period`, message: 'period must be greater than 0' }) + } + }) +} + export const validateSettings = (settings: Settings): ValidationIssue[] => { const issues: ValidationIssue[] = [] @@ -644,6 +659,18 @@ export const validateSettings = (settings: Settings): ValidationIssue[] => { } } + const limits = settings.limits + if (limits) { + validateRateLimitPeriods(limits.event?.rateLimits, 'limits.event.rateLimits', issues) + validateRateLimitPeriods(limits.message?.rateLimits, 'limits.message.rateLimits', issues) + validateRateLimitPeriods(limits.connection?.rateLimits, 'limits.connection.rateLimits', issues) + validateRateLimitPeriods(limits.invoice?.rateLimits, 'limits.invoice.rateLimits', issues) + validateRateLimitPeriods(limits.admissionCheck?.rateLimits, 'limits.admissionCheck.rateLimits', issues) + validateRateLimitPeriods(limits.invite?.rateLimits, 'limits.invite.rateLimits', issues) + validateRateLimitPeriods(limits.admin?.rateLimits, 'limits.admin.rateLimits', issues) + validateRateLimitPeriods(limits.admin?.loginRateLimits, 'limits.admin.loginRateLimits', issues) + } + validateShape(loadDefaults(), settings, [], issues) issues.push(...validateAdminNotifications(settings)) diff --git a/test/unit/utils/settings-config.spec.ts b/test/unit/utils/settings-config.spec.ts index bf50ff94f..527be009a 100644 --- a/test/unit/utils/settings-config.spec.ts +++ b/test/unit/utils/settings-config.spec.ts @@ -98,6 +98,30 @@ describe('settings-config', () => { expect(issues.some((issue) => issue.path === 'network')).to.equal(true) }) + describe('rate limit period validation', () => { + const baseSettings = () => + ({ + info: { relay_url: 'wss://test.relay', name: 'test' }, + network: {}, + }) as any + + it('rejects a non-positive period on limits.event.rateLimits', () => { + const settings = baseSettings() + settings.limits = { event: { rateLimits: [{ period: 0, rate: 1 }] } } + + const issues = validateSettings(settings) + expect(issues.some((issue) => issue.path === 'limits.event.rateLimits[0].period')).to.equal(true) + }) + + it('rejects a non-positive period on limits.admin.loginRateLimits', () => { + const settings = baseSettings() + settings.limits = { admin: { loginRateLimits: [{ period: -1, rate: 1 }] } } + + const issues = validateSettings(settings) + expect(issues.some((issue) => issue.path === 'limits.admin.loginRateLimits[0].period')).to.equal(true) + }) + }) + describe('adaptive pow settings', () => { const baseSettings = () => ({ From 0f67db42a5273671913f357d7cec5417e00d0797 Mon Sep 17 00:00:00 2001 From: ABHAY PANDEY Date: Sun, 11 Oct 2026 08:21:56 +0530 Subject: [PATCH 2/2] fix(settings): harden rateLimits validation and expand tests --- src/utils/settings-config.ts | 4 ++ test/unit/utils/settings-config.spec.ts | 57 ++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/utils/settings-config.ts b/src/utils/settings-config.ts index e218bef7f..93fb91498 100644 --- a/src/utils/settings-config.ts +++ b/src/utils/settings-config.ts @@ -579,6 +579,10 @@ const validateRateLimitPeriods = ( return } rateLimits.forEach((entry, index) => { + if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) { + issues.push({ path: `${pathPrefix}[${index}]`, message: 'rateLimits entry must be an object' }) + return + } if (!(entry.period > 0)) { issues.push({ path: `${pathPrefix}[${index}].period`, message: 'period must be greater than 0' }) } diff --git a/test/unit/utils/settings-config.spec.ts b/test/unit/utils/settings-config.spec.ts index 527be009a..d9179f35d 100644 --- a/test/unit/utils/settings-config.spec.ts +++ b/test/unit/utils/settings-config.spec.ts @@ -105,20 +105,65 @@ describe('settings-config', () => { network: {}, }) as any - it('rejects a non-positive period on limits.event.rateLimits', () => { + const nonPositivePeriodCases: { issuePath: string; limits: Record }[] = [ + { + issuePath: 'limits.event.rateLimits[0].period', + limits: { event: { rateLimits: [{ period: 0, rate: 1 }] } }, + }, + { + issuePath: 'limits.message.rateLimits[0].period', + limits: { message: { rateLimits: [{ period: 0, rate: 1 }] } }, + }, + { + issuePath: 'limits.connection.rateLimits[0].period', + limits: { connection: { rateLimits: [{ period: -1, rate: 1 }] } }, + }, + { + issuePath: 'limits.invoice.rateLimits[0].period', + limits: { invoice: { rateLimits: [{ period: 0, rate: 1 }] } }, + }, + { + issuePath: 'limits.admissionCheck.rateLimits[0].period', + limits: { admissionCheck: { rateLimits: [{ period: 0, rate: 1 }] } }, + }, + { + issuePath: 'limits.invite.rateLimits[0].period', + limits: { invite: { rateLimits: [{ period: 0, rate: 1 }] } }, + }, + { + issuePath: 'limits.admin.rateLimits[0].period', + limits: { admin: { rateLimits: [{ period: 0, rate: 1 }] } }, + }, + { + issuePath: 'limits.admin.loginRateLimits[0].period', + limits: { admin: { loginRateLimits: [{ period: -1, rate: 1 }] } }, + }, + ] + + for (const { issuePath, limits } of nonPositivePeriodCases) { + it(`rejects a non-positive period on ${issuePath.replace('[0].period', '')}`, () => { + const settings = baseSettings() + settings.limits = limits + + const issues = validateSettings(settings) + expect(issues.some((issue) => issue.path === issuePath)).to.equal(true) + }) + } + + it('accepts a positive period on limits.event.rateLimits', () => { const settings = baseSettings() - settings.limits = { event: { rateLimits: [{ period: 0, rate: 1 }] } } + settings.limits = { event: { rateLimits: [{ period: 60_000, rate: 10 }] } } const issues = validateSettings(settings) - expect(issues.some((issue) => issue.path === 'limits.event.rateLimits[0].period')).to.equal(true) + expect(issues.some((issue) => issue.path.startsWith('limits.event.rateLimits'))).to.equal(false) }) - it('rejects a non-positive period on limits.admin.loginRateLimits', () => { + it('rejects a null entry in limits.event.rateLimits without throwing', () => { const settings = baseSettings() - settings.limits = { admin: { loginRateLimits: [{ period: -1, rate: 1 }] } } + settings.limits = { event: { rateLimits: [null] } } const issues = validateSettings(settings) - expect(issues.some((issue) => issue.path === 'limits.admin.loginRateLimits[0].period')).to.equal(true) + expect(issues.some((issue) => issue.path === 'limits.event.rateLimits[0]')).to.equal(true) }) })