Skip to content

feat(security): complete logger, Horizon env, Prisma timeout and helmet hardening (#379, #380, #381, #382) - #853

Merged
Cjay-Cyber-2 merged 1 commit into
mergepay:mainfrom
42natus:logging_serializer
Oct 19, 2026
Merged

Cjay-Cyber-2 merged 1 commit into
mergepay:mainfrom
42natus:logging_serializer

Conversation

@42natus

@42natus 42natus commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Completes and locks down the four security-hardening chores in the backend:

Issue Deliverable
#379 Every service/worker logger now goes through the shared Pino factory, so the req/res/err serializers and credential redaction apply to all log streams — not just Fastify's. A source scan keeps it that way.
#380 The Prisma connection pool / query timeout configuration is pinned by tests (defaults, overrides, and rejection of values that would disable the protection).
#381 HORIZON_URL/HORIZON_URLS must be valid http(s) URLs at startup, and STELLAR_NETWORK accepts TESTNET/PUBLIC case-insensitively.
#382 Helmet security headers are verified on real API and root responses, including x-powered-by removal.

#379 — Pino request logging serializer configuration

  • Migrated 10 bare pino({ name }) loggers (src/services/stellar.ts, horizonService.ts, exchange.ts, treasuryService.ts, settlement-reconciliation.ts, sep24-anchor-token.ts, src/worker/index.ts, reconciliation.ts, tasks/balance-sync.ts, tasks/tx-status-sync.ts) to createLogger() from src/lib/logger.ts, which attaches LOGGER_SERIALIZERS (req, res, err, tx-hash) and REDACT_PATHS.
  • Fixed a latent bug in createLogger: it passed level: undefined, which Pino 9 rejects (default level:undefined must be included in custom levels). The key is now omitted when no level is given, so Pino's info default applies.
  • Added a source-scan test (tests/logger-serializers.test.ts) that fails if any module outside src/lib/logger.ts constructs a bare pino() logger. The existing wiring test already proves requests carrying Authorization/Cookie (proxy-authorization, x-api-key, set-cookie too) log [REDACTED] while req.id, method, url, and telemetry headers survive.

Checklist:

  • Serializers configured to redact authorization and cookie headers (src/lib/logger.ts, src/lib/serializers.ts)
  • Request ID and method/path preserved (unit + Fastify integration assertions)
  • Verification test proving sensitive headers do not appear in serialized output
  • Redaction applies to service/worker log streams, not only HTTP request logs

#380 — Prisma connection timeout configuration

src/db.ts already appends connection_limit, connect_timeout, and pool_timeout to DATABASE_URL and wraps every query in queryTimeoutMiddleware(env.DATABASE_QUERY_TIMEOUT_MS). This PR adds the missing configuration-level proof in tests/config.test.ts:

  • Fail-fast defaults: DATABASE_QUERY_TIMEOUT_MS=10000, DATABASE_CONNECT_TIMEOUT_SECONDS=10, DATABASE_POOL_TIMEOUT_SECONDS=10, DATABASE_CONNECTION_LIMIT=5
  • Explicit overrides accepted
  • 0, negative, and non-numeric values rejected at startup
  • Database connectivity checks still pass (tests/db-health.test.ts, tests/db-timeouts.test.ts)
  • npm run prisma:generate / npm run prisma:validate succeed

#381 — Stellar network configuration validation

  • New horizonUrlSchema: a well-formed URL and http:/https: only. ftp://, file://, and wss:// Horizon endpoints are now rejected at startup instead of failing at the first request.
  • HORIZON_URLS (failover list) holds every entry to the same rule, with an error naming the offending entry.
  • STELLAR_NETWORK accepts testnet/public and TESTNET/PUBLIC, normalizing to lowercase before the existing Horizon/network consistency check runs.
  • The refine callback is non-throwing for malformed input (verified: it previously escaped as a raw TypeError: Invalid URL instead of a field-level Zod issue).

Checklist:

  • Zod ensures HORIZON_URL is a valid HTTP/HTTPS URL and STELLAR_NETWORK is testnet/public
  • Invalid URLs rejected at startup with actionable messages (tests/config.test.ts)
  • Network/URL mismatch still rejected, including for uppercase input

#382 — Helmet security headers

The helmet registration in src/app.ts was already explicitly tuned; this PR extends tests/security.test.ts so the guarantees are tested where the issue asks for them:

  • Standard headers asserted on a real versioned API response (/api/v1/groups → 401) and on the root 404
  • x-content-type-options, x-frame-options, HSTS (max-age=31536000; includeSubDomains), referrer-policy verified
  • x-powered-by is removed
  • Swagger UI (/docs) remains reachable with headers intact

Checklist

  • Read and followed the Contributing Guidelines
  • npm run lint passes (0 errors)
  • npm run build passes
  • npm test passes offline — 205 files, 4169 tests passed | 22 skipped
  • New endpoints validated with Zod + permission checks (n/a — no new endpoints)
  • Audit logs added for state-changing actions (n/a — no state changes)
  • Contract changes mirrored in mergepay-web (n/a — no contract changes)
  • No secrets committed

How was this verified?

npm run lint          # 0 errors (pre-existing warnings only)
npm run build         # tsc clean
npm test              # 205 files, 4169 passed | 22 skipped
npm run prisma:generate
DATABASE_URL=... npm run prisma:validate

Focused suites: tests/config.test.ts, tests/logger-serializers.test.ts, tests/security.test.ts, tests/db-timeouts.test.ts, tests/db-health.test.ts.

Related issues

Closes #379
Closes #380
Closes #381
Closes #382

…et hardening (mergepay#379, mergepay#380, mergepay#381, mergepay#382)

- mergepay#379: route every service and worker logger through the shared
  createLogger factory so the request/response serializers and credential
  redaction apply to all log streams; fix createLogger throwing when level
  is omitted (Pino 9 rejects an explicit undefined level); add a source
  scan that fails any bare pino() construction outside src/lib/logger.ts.
- mergepay#380: pin the Prisma connection pool/timeout defaults and the
  non-positive-value rejection rules with config tests; the datasource URL
  parameters and query-timeout middleware in src/db.ts were already wired.
- mergepay#381: reject non-http(s) HORIZON_URL and HORIZON_URLS entries at startup
  and accept TESTNET/PUBLIC case-insensitively, normalizing to lowercase.
- mergepay#382: extend the helmet integration suite to API and root responses and
  assert x-powered-by is removed.
@drips-wave

drips-wave Bot commented Oct 19, 2026

Copy link
Copy Markdown

@42natus 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

@Cjay-Cyber-2 Cjay-Cyber-2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good — approving.

@Cjay-Cyber-2 Cjay-Cyber-2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good — approving.

@Cjay-Cyber-2
Cjay-Cyber-2 merged commit 1e03287 into mergepay:main Oct 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants