Skip to content

feat(backend): csp report endpoint + structured JSON logging (#133, #136)#200

Open
GWorld57 wants to merge 1 commit into
ChainForgee:mainfrom
GWorld57:fix/GWorld57-csp-report-and-json-logging
Open

feat(backend): csp report endpoint + structured JSON logging (#133, #136)#200
GWorld57 wants to merge 1 commit into
ChainForgee:mainfrom
GWorld57:fix/GWorld57-csp-report-and-json-logging

Conversation

@GWorld57

Copy link
Copy Markdown

Summary\n\nResolves the two open issues assigned to GWorld57 in a single PR.\n\n### Issue #133 — Content-Security-Policy reporting endpoint\n\n* Adds a Public, unthrottled POST /api/v1/csp-report collector that accepts both legacy application/csp-report payloads ({ "csp-report": {...} }) and modern application/reports+json arrays, and gracefully normalises the newer { type, url, body } envelope.\n* Logs every violation through the shared Pino logger at warn level with structured fields (document_uri, violated_directive, blocked_uri, source_file, line/column, original_policy, etc.).\n* Detects spikes: emits a single error-level log entry per 60-second window once more than 25 violations are observed, so monitoring can alert.\n* Adds report-uri /api/v1/csp-report directive to the production CSP emitted by helmet, and registers a JSON body parser in main.ts that understands the CSP and Reporting API content types (Nest/Express do not parse them by default).\n* Adds /api/v1/csp-report to the in-memory rate-limiter exempt list so legitimate spike reports cannot be silenced.\n\n### Issue #136 — Structured JSON logging in production\n\n* LoggerService now emits parseable JSON in NODE_ENV=production and NODE_ENV=test (previous default produced pretty-printed stdout which is not ELK/Datadog/CloudWatch friendly).\n* Development environments retain the colourful pino-pretty transport (no transport in test, preventing jest hanging on worker threads).\n* LOG_LEVEL env var continues to drive Pino level threshold (default info).\n* Sensitive keys are now redacted by Pino itself (redact config with brute paths up to depth 4) AND by the existing log-redaction.util.ts applied to incoming meta arguments as a defense-in-depth — so secrets cannot slip through either deeply nested objects or shallow ones.\n\n## Files\n\n* app/backend/src/logger/logger.service.ts — production/test JSON output, conditional pino-pretty transport, redact integration.\n* app/backend/src/logger/logger.service.spec.ts — new unit tests covering JSON-mode construction, redaction integration, correlation ID propagation, and LOG_LEVEL behaviour.\n* app/backend/src/common/security/security.module.ts — registers the new controller, adds report-uri directive, extends rate-limiter exempt list.\n* app/backend/src/common/security/csp-report.controller.ts — new controller.\n* app/backend/src/common/security/csp-report.controller.spec.ts — new unit tests (legacy/modern/array payloads, spike detection, Public decorator).\n* app/backend/src/main.ts — registers JSON body parser for non-standard CSP content types.\n* app/backend/test/security.e2e-spec.ts — extended to verify production CSP header, POST endpoint, no API key requirement, and rate-limit exemption.\n\n## Validation\n\n* pnpm exec tsc --noEmit — passes (no new errors).\n* pnpm exec prettier --check on changed files — passes.\n* pnpm exec eslint on changed files — passes (no errors, no warnings).\n* The new unit suites and the security.e2e-spec.ts follow the existing test patterns in those files.\n\n## Notes\n\n* Validation surfaced a pre-existing runtime incompatibility between Jest 30.4 and jest-mock-extended 4 (_moduleMocker.clearMocksOnScope is not a function during Runtime.resetModules) that breaks every spec in app/backend — including existing files unrelated to this PR. The new specs are written to follow existing conventions so they will pass once that package-level issue is resolved (likely a bump of jest-mock-extended to a version that ships a Jest-30-compatible ModuleMocker). Worth filing separately; outside the scope of #133/#136.\n\nCloses #133\nCloses #136

…hainForgee#133, Closes ChainForgee#136)

Resolves two security/observability gaps assigned to GWorld57.

* Switches Pino logger to JSON output in production/test and retains pretty transport in development; honours LOG_LEVEL env var and applies redact paths for sensitive keys (password, token, secret, authorization, apikey, api_key, privatekey, private_key, creditcard, ssn). The existing redactLogData util is also applied defensively on incoming meta before Pino serialisation.

* Adds a /api/v1/csp-report endpoint that accepts both legacy `application/csp-report` and modern `application/reports+json` payloads, logs each violation at warn level, and emits an error-level spike log when more than 25 violations are observed in a 60-second sliding window. The route is @public, @SkipThrottle, and exempt from the in-memory rate limiter so genuine browser reports cannot be silenced by abuse thresholds.

* Updates the production CSP to include `report-uri /api/v1/csp-report` so violations become observable for monitoring/alerting, and registers a JSON body parser in main.ts that understands application/csp-report + application/reports+json Content-Types.

* Adds csp-report.controller.spec.ts, logger.service.spec.ts, and extends test/security.e2e-spec.ts to cover the new directive, payload shapes, and rate-limit exemption.

Closes ChainForgee#133

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant