Skip to content

[BE-30] Security Hardening: Rate Limiting per Route, Request Signing & Helmet Headers #1016

Description

@mftee

Overview

The backend has a global rate limiter but no per-route limits, no HMAC request signing for webhook endpoints, and no HTTP security headers beyond what NestJS provides by default. This issue completes the backend security hardening. Production deployments will depend on this work.

Technical Details

1. Per-Route Rate Limits

Extend the existing @Throttle decorators to add specific limits per endpoint category:

  • Auth endpoints (/auth/login, /auth/register, /auth/refresh): 5 requests per minute per IP (already partially done — verify and tighten)
  • Password reset (/auth/forgot-password, /auth/reset-password): 3 per 15 minutes per email
  • Document upload (/documents, /carriers/documents): 10 per hour per user
  • Bid submission (/shipments/:id/bids): 20 per hour per user (carriers can't spam bids)
  • Message sending (/conversations/:id/messages): 30 per minute per user (already in [BE-13] — verify)
  • CSV/PDF export: 5 per hour (already in [BE-20] — verify)
  • /stellar/escrow/*: 10 per hour per user
  • /admin/*: 60 per minute per admin user (higher limit for admin tooling)

Create a throttle.constants.ts file listing all rate limit values for easy adjustment.

2. Helmet Security Headers

Install @fastify/helmet or use NestJS Helmet middleware (if using Express):

// In main.ts comments say NOT to touch — add a note to open a follow-up if helmet needs main.ts changes.
// Instead, add a custom middleware in app.module.ts providers using NestMiddleware

Actually, since main.ts is off-limits per the project rules, implement Helmet as a NestMiddleware and apply it in AppModule.configure():

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(helmet()).forRoutes('*');
  }
}

Headers to configure:

  • Content-Security-Policy: restrict to self + cdn sources
  • X-Frame-Options: DENY
  • X-Content-Type-Options: nosniff
  • Strict-Transport-Security (HSTS)
  • Disable X-Powered-By: Express

3. Webhook Signature Validation (for all external webhooks)

Ensure all webhook endpoints validate signatures:

  • Stripe webhook (POST /payments/webhook): validate stripe-signature header using stripe.webhooks.constructEvent() — already planned in [BE-23], verify this is implemented correctly
  • Stellar events (if applicable): validate via the platform secret

4. SQL Injection & Input Hardening Audit

Review all TypeORM QueryBuilder calls in:

  • GET /shipments (filter queries)
  • GET /admin/users
  • GET /carriers
  • GET /notifications

Ensure all user-supplied strings are passed as TypeORM parameters (never string-interpolated into query strings). Fix any instances where .where(field = ${variable}) is used instead of .where('field = :val', { val: variable }).

5. Environment Variable Validation (Joi Schema)

Add Joi validation for all new env vars added in issues BE-10 through BE-29:

  • Extend the existing validation.schema.ts (or create it if not present)
  • All Twilio, Stripe, Stellar, Web Push, and SMTP env vars should be required in production (Joi.when('NODE_ENV', { is: 'production', then: Joi.required() }))

Acceptance Criteria

  • /auth/login returns 429 after 5 requests per minute from the same IP
  • /auth/forgot-password returns 429 after 3 requests per 15 minutes for the same email
  • Document upload endpoint returns 429 after 10 uploads per hour
  • Helmet security headers are present in API responses (verify with curl -I)
  • X-Powered-By header is removed from all responses
  • No TypeORM query builder call uses string interpolation for user input
  • All new env vars are validated in the Joi schema
  • Stripe webhook endpoint returns 400 for requests with an invalid signature

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions