fix: validate admin stats pagination#1851
Conversation
📝 WalkthroughWalkthroughThis pull request adds input validation for admin stats endpoints by introducing max limit constants, exporting a new validation schema with constraints for pagination parameters, updating request parsing to validate inputs, refactoring analytics limit normalization to accept fallback values, and adding unit tests for input validation and limit normalization. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
tests/admin-stats.unit.test.ts (2)
12-53: Use concurrent Vitest cases in this file.Switch these tests to
it.concurrent(...)/it.concurrent.each(...)to match the test-runner convention for parallelized.test.tssuites.As per coding guidelines, "ALL TEST FILES RUN IN PARALLEL. Use `it.concurrent()` instead of `it()` to run tests in parallel within the same file."Suggested change
- it.each([ + it.concurrent.each([ ['sql injection string limit', { limit: '1 UNION SELECT 1' }], ['decimal limit', { limit: 1.5 }], ['negative offset', { offset: -1 }], ['oversized limit', { limit: MAX_ADMIN_STATS_LIMIT + 1 }], ['oversized offset', { offset: MAX_ADMIN_STATS_OFFSET + 1 }], ])('rejects %s', (_label, body) => { @@ - it('accepts bounded integer pagination', () => { + it.concurrent('accepts bounded integer pagination', () => { @@ - it.each([ + it.concurrent.each([ ['string injection', '1 UNION SELECT 1', 100], ['negative number', -10, 100], ['zero', 0, 100], ['decimal', 12.8, 12], ['oversized', 99_999_999, 50_000], ])('normalizes %s', (_label, input, expected) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/admin-stats.unit.test.ts` around lines 12 - 53, The test cases in this file use synchronous it()/it.each() which violates the project convention that test files run in parallel; update all test declarations to use Vitest's concurrent variants (replace it.each(...) with it.concurrent.each(...), and it(...) with it.concurrent(...)) for the blocks referencing adminStatsBodySchema, normalizeAnalyticsLimit, MAX_ADMIN_STATS_LIMIT, MAX_ADMIN_STATS_OFFSET and baseBody so the "rejects ..." parametrized suite and the "accepts bounded integer pagination" and "normalizes ..." cases run concurrently.
2-2: Fix named import order to satisfy ESLint.This line currently violates
perfectionist/sort-named-imports.Suggested change
-import { MAX_ADMIN_STATS_LIMIT, MAX_ADMIN_STATS_OFFSET, adminStatsBodySchema } from '../supabase/functions/_backend/private/admin_stats.ts' +import { adminStatsBodySchema, MAX_ADMIN_STATS_LIMIT, MAX_ADMIN_STATS_OFFSET } from '../supabase/functions/_backend/private/admin_stats.ts'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/admin-stats.unit.test.ts` at line 2, Reorder the named imports on the import from ../supabase/functions/_backend/private/admin_stats.ts to satisfy perfectionist/sort-named-imports: place the imported symbols in alphabetical order (e.g., adminStatsBodySchema, MAX_ADMIN_STATS_LIMIT, MAX_ADMIN_STATS_OFFSET) in the import statement so ESLint no longer flags the line.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@tests/admin-stats.unit.test.ts`:
- Around line 12-53: The test cases in this file use synchronous it()/it.each()
which violates the project convention that test files run in parallel; update
all test declarations to use Vitest's concurrent variants (replace it.each(...)
with it.concurrent.each(...), and it(...) with it.concurrent(...)) for the
blocks referencing adminStatsBodySchema, normalizeAnalyticsLimit,
MAX_ADMIN_STATS_LIMIT, MAX_ADMIN_STATS_OFFSET and baseBody so the "rejects ..."
parametrized suite and the "accepts bounded integer pagination" and "normalizes
..." cases run concurrently.
- Line 2: Reorder the named imports on the import from
../supabase/functions/_backend/private/admin_stats.ts to satisfy
perfectionist/sort-named-imports: place the imported symbols in alphabetical
order (e.g., adminStatsBodySchema, MAX_ADMIN_STATS_LIMIT,
MAX_ADMIN_STATS_OFFSET) in the import statement so ESLint no longer flags the
line.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2f880f77-95b3-4d24-99bd-6bcaf9db8897
📒 Files selected for processing (3)
supabase/functions/_backend/private/admin_stats.tssupabase/functions/_backend/utils/cloudflare.tstests/admin-stats.unit.test.ts
|



Summary (AI generated)
limitandoffsetin/private/admin_statsbefore using request datagetAdminOrgMetricsas defense in depthMotivation (AI generated)
The oldest in-scope triaged advisory,
GHSA-6ffx-8hjj-jhhf, reported that/private/admin_statsaccepted an unvalidatedlimitvalue and passed it into Analytics Engine SQL interpolation. The route now rejects malformed pagination inputs and the downstream helper also normalizes the limit so future callers cannot reintroduce the sink as easily.Business Impact (AI generated)
This closes an admin-only query injection path in the analytics dashboard and reduces the risk of analytics schema probing or expensive query abuse. It is a low-severity issue, but the fix is small, safe, and improves trust in the admin surface.
Test Plan (AI generated)
bun lint:backendbunx vitest run tests/admin-stats.unit.test.tsbun test:backendcurrently fails in this worktree on pre-existing suites unrelated to this patch:tests/organization-api.test.tstests/email-preferences.test.tstests/error-cases.test.tstests/trigger-error-cases.test.tsGenerated with AI
Summary by CodeRabbit
Release Notes