Skip to content

Fix shared validation and webhook-delivery security issues - #637

Merged
therealjhay merged 2 commits into
Betta-Pay:mainfrom
Mutech939:fix/shared-validation-webhook-security-issues-528-526-516-517
Sep 1, 2026
Merged

Fix shared validation and webhook-delivery security issues#637
therealjhay merged 2 commits into
Betta-Pay:mainfrom
Mutech939:fix/shared-validation-webhook-security-issues-528-526-516-517

Conversation

@Mutech939

Copy link
Copy Markdown
Contributor

Summary

This PR addresses four security and reliability issues in shared packages:

Changes

Issue #528: Metrics Server Authentication

File: shared/validation/metrics-server.ts

Added optional Bearer token authentication:

  • New scrapeToken parameter in StartMetricsServerOptions
  • When set, requests must include Authorization: Bearer <token>
  • Unauthorized requests receive 401 with WWW-Authenticate header
  • When omitted, serves unauthenticated (network isolation required)
  • Logs auth status on startup

Security benefit: Prevents internal metrics leakage on misconfigured networks.

Tests added (4 test cases):

  • Unauthorized scrape rejection (no token, wrong token, malformed header)
  • Valid token acceptance
  • Unauthenticated mode verification
  • Auth status logging

Issue #526: Encryption with Per-Encryption Salt

Files:

  • shared/validation/encryption.ts (new)
  • shared/validation/encryption.test.ts (new)

Created secure encryption module:

  • Per-encryption salt: Fresh random 16-byte salt for each encryption
  • Fresh IV/nonce: 12 bytes per encryption (GCM recommended size)
  • Key derivation: PBKDF2 with 100,000 iterations (SHA-256)
  • Cipher: AES-256-GCM (authenticated encryption)
  • Output format: base64(salt:iv:authTag:ciphertext)

Security properties:

  • Identical plaintexts produce different ciphertexts (no metadata leakage)
  • Salt uniqueness verified under rapid successive calls (100 encryptions all unique)
  • Authentication prevents tampering
  • High iteration count resists brute-force attacks

API:

export function encrypt(plaintext: string, password: string): string
export function decrypt(encrypted: string, password: string): string
export function verifyPassword(encrypted: string, password: string): boolean

Tests added (15 test cases):

  • Salt uniqueness across 100 rapid encryptions
  • Round-trip preservation (unicode, special chars, empty string, 10KB data)
  • Tamper detection (wrong password, modified ciphertext)
  • Edge cases (invalid base64, truncated data)
  • Password verification without plaintext exposure

Issue #516: Env-Configurable Webhook Concurrency

File: shared/webhook-delivery/index.ts

Made concurrency tunable without code changes:

  • New WEBHOOK_CONCURRENCY environment variable
  • resolveWebhookConcurrency() helper reads env with fallback
  • Defaults to 10 when unset or invalid
  • Explicit concurrency option still overrides env var

Operator benefit: Tune concurrency based on worker resources without redeploying code.

Usage:

# Small worker - reduce concurrency
WEBHOOK_CONCURRENCY=3 node worker.js

# Large worker - increase concurrency  
WEBHOOK_CONCURRENCY=25 node worker.js

Tests added (5 test cases):

  • resolveWebhookConcurrency() with valid/invalid env values
  • Worker creation with env-driven default
  • Explicit option precedence over env var
  • Invalid value fallback (non-numeric, negative, zero)

Issue #517: Socket Cleanup in Finally Block

File: shared/webhook-delivery/index.ts

Fixed potential timer/socket leaks:

  • Before: clearTimeout in both try and catch blocks (race condition on error path)
  • After: clearTimeout in finally block (guaranteed cleanup)
  • Ensured AbortController.abort() called on all exit paths
  • Prevents leaked timers/sockets on failure, timeout, or non-2xx response

Reliability benefit: No resource leaks under failure scenarios.

Tests added (3 test cases):

  • Timer cleanup verification on success path
  • Timer cleanup verification on error path
  • Leak prevention on timeout abort

Testing

Test Coverage Summary

  • 27 new test cases added across all issues
  • All tests pass locally with tape + ts-node
  • Coverage includes happy paths, edge cases, and failure scenarios

CI Pipeline

  • TypeScript build verification
  • Full test suite execution
  • Linting and type checking

Acceptance Criteria

Issue #528

  • Metrics require auth when scrapeToken provided
  • Test covers unauthorized scrape rejection
  • pnpm build passes

Issue #526

  • Each encryption uses fresh salt
  • No repeated metadata for identical plaintexts
  • pnpm build passes

Issue #516

  • Concurrency env-configurable via WEBHOOK_CONCURRENCY
  • Per-merchant limits enforceable (via explicit option override)
  • pnpm --filter @bettapay/webhook-delivery build passes

Issue #517

  • No leaked timers/sockets on failure
  • Finally-based cleanup in place
  • pnpm --filter @bettapay/webhook-delivery build passes

Migration Notes

Metrics Server Authentication

Backward compatible - existing deployments continue to work (unauthenticated mode).

To enable auth:

startMetricsServer({
  appPort: 3000,
  contentType: promClient.register.contentType,
  getMetrics: () => promClient.register.metrics(),
  scrapeToken: process.env.METRICS_SCRAPE_TOKEN, // New optional param
  log: fastify.log,
});

Configure Prometheus:

scrape_configs:
  - job_name: 'bettapay'
    authorization:
      type: Bearer
      credentials: '<METRICS_SCRAPE_TOKEN>'

Encryption Module

New module - no migration needed. Use for encrypting sensitive data at rest:

import { encrypt, decrypt } from '@bettapay/validation';

const encrypted = encrypt('sensitive-data', process.env.ENCRYPTION_PASSWORD);
// Store encrypted in database
const decrypted = decrypt(encrypted, process.env.ENCRYPTION_PASSWORD);

Webhook Concurrency

Backward compatible - defaults to 10 when WEBHOOK_CONCURRENCY unset.

To tune:

# Production deployment
WEBHOOK_CONCURRENCY=20 npm start

# Development/testing
WEBHOOK_CONCURRENCY=2 npm run dev

Socket Cleanup

Transparent fix - no code changes required. Workers automatically benefit from leak prevention.

Checklist

  • Code follows project conventions
  • Tests added for all new functionality (27 new test cases total)
  • Existing tests pass
  • Documentation updated (code comments, test descriptions)
  • No breaking changes
  • Commit messages clear and descriptive

Related Issues

Closes #528
Closes #526
Closes #516
Closes #517

k-deejah and others added 2 commits August 31, 2026 02:23
)

Added optional scrapeToken parameter to startMetricsServer():
- When scrapeToken is provided, requests must include 'Authorization: Bearer <token>'
- Unauthorized requests receive 401 with WWW-Authenticate header
- When scrapeToken is omitted, metrics served unauthenticated (network isolation required)
- Logs auth status on startup

Tests added:
- Unauthorized scrape rejection (no token, wrong token, malformed header)
- Valid token acceptance
- Unauthenticated mode when token not configured
- Auth status logging verification

Operators can now:
- Secure metrics endpoint with bearer token auth
- Configure Prometheus scraper with bearer_token config
- Or rely on network isolation (firewall/localhost binding)

Closes Betta-Pay#528
@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@Mutech939 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@therealjhay
therealjhay merged commit 9729dfb into Betta-Pay:main Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants