Skip to content

fix(security): stop serving compiled backend at HTTP root, enforce validation and throttling - #124

Open
dogancanbakir wants to merge 2 commits into
mainfrom
claude/security-hardening
Open

dogancanbakir wants to merge 2 commits into
mainfrom
claude/security-hardening

Conversation

@dogancanbakir

@dogancanbakir dogancanbakir commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Fixes three verified backend security issues, plus a broken Docker healthcheck, in two commits.

1. Stop serving the compiled backend at the HTTP root (#123)

What: Removed app.use(express.static(path.join(__dirname))) from backend/src/main.ts (and the now-unused express / path imports).

Why: At runtime __dirname is the compiled output directory, so the entire compiled backend was mounted as static files at the server root — any unauthenticated client could fetch GET /main.js, GET /app.module.js, etc. Because tsconfig.json sets sourceMap: true, the matching .js.map files effectively exposed the original TypeScript source as well. Nothing in the repo relied on this static mount (the frontend is a separate Next.js app).

2. Register a global ValidationPipe (#38)

What: Added app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })) in backend/src/main.ts, and removed the now-redundant per-controller @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) from the meetings, tasks, subtasks and tags controllers (they used the exact same options, so behavior there is unchanged).

Why: Previously only those 4 controllers validated input; the other controllers (auth, users, api-keys, communities, clubs, activity-feed, notifications, notification-preferences, moderation, gamification) accepted unvalidated bodies and their DTO decorators were ignored. All @Body() parameters across controllers are typed DTOs, so the global pipe now validates everywhere.

Note on forbidNonWhitelisted: deliberately left off, following the recommendation in #38. whitelist: true already strips unknown properties (closing the mass-assignment hole), whereas forbidNonWhitelisted: true would turn previously-accepted extra fields into hard 400s for existing clients that were never tested against validation. It can be enabled later as a follow-up once clients are known-clean.

3. Enforce the configured rate limiting (#39)

What: Added backend/src/shared/guards/global-throttler.guard.ts and registered it as APP_GUARD in backend/src/app.module.ts, so the existing ThrottlerModule.forRoot config (short/medium/long limits) is actually enforced on every HTTP route.

Why: The throttler was configured but no global guard existed, so most endpoints — including auth — had no rate limiting.

Avoiding double-throttling: the clubs, meetings, notifications and tasks controllers already apply ApiKeyThrottlerGuard via @UseGuards. That guard must stay controller-level because it runs after the auth guards and keys limits on the resolved API key (req.user.apiKeyId); a global guard runs before auth and would only ever see the IP. GlobalThrottlerGuard therefore skips any route whose class/handler already applies a ThrottlerGuard subclass, so those routes keep their existing behavior and requests are not counted twice against the same throttler storage. Non-HTTP contexts (the notifications WebSocket gateway) are also skipped, since ThrottlerGuard only understands HTTP requests.

4. Fix the Docker healthcheck (separate commit)

What: backend/Dockerfile healthcheck changed from ps aux | grep node || exit 1 to pgrep node || exit 1.

Why: grep node always matches its own process in the ps output, so the healthcheck could never fail — a dead app still reported healthy. pgrep (available in Alpine busybox) matches only the node process and exits non-zero when it is gone. (The repo has no dedicated health endpoint, so a process check remains the simplest honest option.)

Closes #123, Closes #38, Closes #39

Testing

  • npm ci && npm run build in backend/: passes.
  • npm test: 42/42 tests pass. Two suites (community.entity.spec.ts, community-member.entity.spec.ts) fail to load with Cannot find module 'src/shared/domain/base-entity' — a pre-existing jest moduleNameMapper gap that reproduces identically on main (verified by stashing these changes and re-running).
  • e2e tests (npm run test:e2e) were not run: they require live Postgres/Redis, which are not available in this environment.
  • Verified that all remaining @Body() parameters are decorated DTO classes, so the global pipe does not break DTO-less endpoints (GET /api takes no body).

Summary by CodeRabbit

  • New Features
    • API requests are now protected by application-wide rate limits, while routes with their own throttling rules retain those rules.
  • Bug Fixes
    • Request validation and value conversion are applied consistently across the API, helping reject unexpected input and normalize accepted values.

…lidation and throttling

- Remove app.use(express.static(path.join(__dirname))) from backend/src/main.ts,
  which mounted the entire compiled dist/ (including .js.map source maps, since
  tsconfig sets sourceMap: true) as static files at the server root, exposing the
  full backend source to any unauthenticated client. Drop the now-unused express
  and path imports. Closes #123.

- Register a global ValidationPipe (whitelist: true, transform: true) in main.ts
  so every controller validates request bodies against its DTO decorators, and
  remove the now-redundant per-controller @UsePipes from the meetings, tasks,
  subtasks and tags controllers (they used the exact same options).
  forbidNonWhitelisted is intentionally left off, matching the issue's
  recommendation: whitelist already strips unknown properties, while
  forbidNonWhitelisted would turn previously-accepted extra fields into 400s
  for existing clients. Closes #38.

- Enforce the ThrottlerModule config by registering GlobalThrottlerGuard as
  APP_GUARD in app.module.ts. The guard skips routes that already apply their
  own ThrottlerGuard subclass via @UseGuards (ApiKeyThrottlerGuard on the clubs,
  meetings, notifications and tasks controllers, which must run after the auth
  guards to key limits on the resolved API key), so those routes are not counted
  twice against the same throttler storage. Non-HTTP contexts (WebSockets) are
  skipped. Closes #39.
'ps aux | grep node' always exits 0 because grep matches its own process in
the ps output, so the healthcheck could never report an unhealthy container.
Use pgrep (available in Alpine's busybox), which matches only the node
process and exits non-zero when it is gone.
@coderabbitai

coderabbitai Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e122ba54-0537-42d6-be73-9dae2c57ec20
Commits

Reviewing files that changed from the base of the PR and between 9cb3d8e and 2372c71.

Files selected for processing: 8
  • backend/Dockerfile
  • backend/src/app.module.ts
  • backend/src/main.ts
  • backend/src/modules/meetings/presentation/controllers/meetings.controller.ts
  • backend/src/modules/tasks/presentation/controllers/subtasks.controller.ts
  • backend/src/modules/tasks/presentation/controllers/tags.controller.ts
  • backend/src/modules/tasks/presentation/controllers/tasks.controller.ts
  • backend/src/shared/guards/global-throttler.guard.ts

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 516465ec-abfa-4827-8bd3-db6fba5d437a
Commits

Reviewing files that changed from the base of the PR and between dd9121a and 9cb3d8e.

Files selected for processing: 8
  • backend/Dockerfile
  • backend/src/app.module.ts
  • backend/src/main.ts
  • backend/src/modules/meetings/presentation/controllers/meetings.controller.ts
  • backend/src/modules/tasks/presentation/controllers/subtasks.controller.ts
  • backend/src/modules/tasks/presentation/controllers/tags.controller.ts
  • backend/src/modules/tasks/presentation/controllers/tasks.controller.ts
  • backend/src/shared/guards/global-throttler.guard.ts
Files with no reviewable changes: 2
  • backend/src/modules/tasks/presentation/controllers/tags.controller.ts
  • backend/src/modules/tasks/presentation/controllers/subtasks.controller.ts

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

The backend now applies validation and throttling globally, removes static serving of compiled files, and updates the Docker health check to use pgrep node.

Changes

Global request validation

Layer / File(s) Summary
Configure global validation
backend/src/main.ts, backend/src/modules/meetings/presentation/controllers/meetings.controller.ts, backend/src/modules/tasks/presentation/controllers/*controller.ts
The application configures a global validation pipe with whitelisting and transformation. The meetings, tasks, subtasks, and tags controllers no longer configure their own validation pipes.

Global throttling

Layer / File(s) Summary
Implement and register the global guard
backend/src/shared/guards/global-throttler.guard.ts, backend/src/app.module.ts
The new guard bypasses non-HTTP requests and routes with controller- or handler-level throttler guards. Other HTTP requests pass to the parent throttler guard. AppModule registers the guard globally.

Compiled-file static serving

Layer / File(s) Summary
Remove compiled-directory static serving
backend/src/main.ts
The application no longer serves its compiled directory as static files.

Docker health check

Layer / File(s) Summary
Update process check
backend/Dockerfile
The health check uses pgrep node and retains exit status 1 when the check fails.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: High

Sequence Diagram(s)

sequenceDiagram
  participant HTTPRequest
  participant GlobalThrottlerGuard
  participant ThrottlerGuard
  HTTPRequest->>GlobalThrottlerGuard: canActivate(context)
  alt Non-HTTP request or local ThrottlerGuard metadata
    GlobalThrottlerGuard-->>HTTPRequest: allow request
  else HTTP request without local ThrottlerGuard metadata
    GlobalThrottlerGuard->>ThrottlerGuard: canActivate(context)
    ThrottlerGuard-->>GlobalThrottlerGuard: throttling result
    GlobalThrottlerGuard-->>HTTPRequest: return result
  end
Loading

Suggested reviewers: arjinalbay

Merge Risk: Minimal · previous review 9cb3d

This PR stops serving compiled backend files, applies validation globally, adds global throttling, and makes the Docker healthcheck able to fail. No new merge-blocking regression was found. Meetings and tasks routes still have no rate limit for requests that fail authentication. That was already true before this change, so it is a possible follow-up rather than a blocker.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title names the two main changes: stopping the compiled backend from being served at the HTTP root, and enforcing validation and throttling. The changeset matches both. The title also includes the…
Linked Issues check Passed The PR removes the compiled-backend static mount from backend/src/main.ts and registers ValidationPipe globally with whitelist: true and transform: true [#123, #38]. It removes the redundant c…
Out of Scope Changes check Passed The reviewed changes stay within the linked security fixes. The Docker healthcheck supports #123. Controller import and decorator removal supports global validation. The new guard and its registration…
Full details: Title check

Explanation

The title names the two main changes: stopping the compiled backend from being served at the HTTP root, and enforcing validation and throttling. The changeset matches both. The title also includes the 'fix(security)' prefix. Its wording is long, but it is clear and specific. The changes match the title: the static-file mount is removed from main.ts, a global ValidationPipe is added, per-controller pipes are removed, and a GlobalThrottlerGuard is registered in app.module.ts. The Docker healthcheck change is not named in the title, but that is a minor detail.

Full details: Linked Issues check

Explanation

The PR removes the compiled-backend static mount from backend/src/main.ts and registers ValidationPipe globally with whitelist: true and transform: true [#123, #38]. It removes the redundant controller-level pipes [#38]. It registers GlobalThrottlerGuard as APP_GUARD, skips non-HTTP contexts, and avoids duplicate throttling for routes with a ThrottlerGuard subclass [#39]. The Docker healthcheck change to pgrep node addresses the related healthcheck defect in #123. The reported build passes, and the reported Jest load failures reproduce on the base branch.

Full details: Out of Scope Changes check

Explanation

The reviewed changes stay within the linked security fixes. The Docker healthcheck supports #123. Controller import and decorator removal supports global validation. The new guard and its registration support #39. The reported tests and build verification support these changes. No unrelated change is established.

Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 1 functions across 5 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

A rabbit checks the server at dawn,
The old process search is gone.
Pipes now greet each request in line,
A guard checks limits at the sign.
No compiled files greet the breeze,
The rabbit nibbles carrots with ease.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant