Skip to content

fix: validate admin stats pagination#1851

Merged
riderx merged 4 commits into
mainfrom
fix/ghsa-6ffx-admin-stats-limit
Mar 25, 2026
Merged

fix: validate admin stats pagination#1851
riderx merged 4 commits into
mainfrom
fix/ghsa-6ffx-admin-stats-limit

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Mar 24, 2026

Summary (AI generated)

  • validate limit and offset in /private/admin_stats before using request data
  • read admin stats inputs only from the validated Zod payload
  • clamp the Cloudflare Analytics Engine limit in getAdminOrgMetrics as defense in depth
  • add unit coverage for pagination validation and limit normalization

Motivation (AI generated)

The oldest in-scope triaged advisory, GHSA-6ffx-8hjj-jhhf, reported that /private/admin_stats accepted an unvalidated limit value 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:backend
  • bunx vitest run tests/admin-stats.unit.test.ts
  • bun test:backend currently fails in this worktree on pre-existing suites unrelated to this patch:
  • tests/organization-api.test.ts
  • tests/email-preferences.test.ts
  • tests/error-cases.test.ts
  • tests/trigger-error-cases.test.ts

Generated with AI

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Strengthened validation for admin statistics API requests with enforced constraints on pagination parameters.
    • Improved robustness of analytics limit handling to prevent invalid data queries.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Admin Stats Validation
supabase/functions/_backend/private/admin_stats.ts
Added exported constants MAX_ADMIN_STATS_LIMIT (50,000) and MAX_ADMIN_STATS_OFFSET (100,000); introduced exported adminStatsBodySchema with validation constraints for limit (1–50,000), offset (0–100,000), and optional string fields; updated request parsing to validate body against schema and extract parameters from parsed data.
Analytics Limit Normalization
supabase/functions/_backend/utils/cloudflare.ts
Exported normalizeAnalyticsLimit function with signature change to accept limit: unknown and fallback parameter; refactored getAdminOrgMetrics to compute safeLimit via normalization and apply it consistently to both MAU and bandwidth query LIMIT clauses.
Test Coverage
tests/admin-stats.unit.test.ts
Added new Vitest test file with 53 lines covering schema validation (invalid limit/offset scenarios: SQL injection strings, decimals, negative values, oversized values) and normalizeAnalyticsLimit behavior (string injection, negative inputs, zero, rounding/truncation, oversized limits).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Limits bound and bounds are tight,
Validation checks ensure what's right,
Offsets dance with limits in a row,
Test cases prove the safe pathways flow,
Fuzzy checks make admin stats aglow! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: validate admin stats pagination' directly and clearly describes the main change—adding validation for pagination inputs in the admin stats endpoint.
Description check ✅ Passed The description includes a detailed AI-generated summary explaining the changes, motivation addressing a specific security advisory, business impact, and a test plan with results, covering all critical sections of the template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ghsa-6ffx-admin-stats-limit

Comment @coderabbitai help to get the list of available commands and usage tips.

@riderx riderx added the codex label Mar 24, 2026
@riderx riderx marked this pull request as ready for review March 25, 2026 03:13
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 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.ts suites.

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) => {
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."
🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4c6a951 and 1167f27.

📒 Files selected for processing (3)
  • supabase/functions/_backend/private/admin_stats.ts
  • supabase/functions/_backend/utils/cloudflare.ts
  • tests/admin-stats.unit.test.ts

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 9b7c72d into main Mar 25, 2026
15 checks passed
@riderx riderx deleted the fix/ghsa-6ffx-admin-stats-limit branch March 25, 2026 03:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant