Skip to content

fix(security): define masked-copy keys and escape the request logger's error line - #5

Merged
Hiprax merged 1 commit into
mainfrom
fix/define-rebuilt-keys-escape-error-line
Sep 28, 2026
Merged

Hiprax merged 1 commit into
mainfrom
fix/define-rebuilt-keys-escape-error-line

Conversation

@Hiprax

@Hiprax Hiprax commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Summary

Closes the 7 open code-scanning alerts on main (6 × js/remote-property-injection in src/redact.ts, 1 × js/log-injection in src/request-middleware.ts) by fixing the defect behind them.

Why they appeared in 1.3.0. The new type fixtures tests/fixtures/types/eopt-express.ts and eopt-node-http.ts mount the middleware, so CodeQL now models its req as a real HTTP request. The same CodeQL version gave 0 results on the 1.3.0 commit with those two fixtures removed. The fixtures stay: that is how applications mount the middleware.

The defect. Every masked copy (request bodies, request and response headers, maskMetaKeys metadata, Error fields, toJSON() output) was built by assigning each caller- or client-chosen key onto a fresh object. Assignment consults Object.prototype, so with Object.prototype frozen (a common prototype-pollution mitigation) a key such as toString threw: the logged request body became "[UNSERIALIZABLE]", a maskMetaKeys entry degraded to _unserializable / _redactionFailed / "[RedactionFailed]", and a mixed-case Constructor header dropped the entry.

Changes

  • The copies now define their keys: an object spread in redactEntries (faster than the old loop), Object.fromEntries elsewhere. Every key is logged as it is; __proto__ / constructor / prototype are still left out, now judged after lowercasing header names (a __PROTO__ header could silently disable a redactPaths header entry with header masking off).
  • neutralizeCallerAccessors keeps assigning on purpose: winston-transport's own Object.assign re-clone follows it, so defining keys there would turn today's degraded line into an exception out of logger.info(). A guard test pins this.
  • The error-path console.error line escapes the method, URL and error message (JSON.stringify without quotes, then Unicode control, format and separator characters as \u escapes). Lossless; an ordinary line prints as before.
  • Documented boundaries: the package must load before Object.prototype is frozen (moment assigns while loading), and winston's own copies (format-carrying transports, info(msg, meta), child loggers, errors()) still assign top-level keys.

Verification

  • New tests were written first and failed for the intended reasons; each fixed site was then reverted (mutation check) and its tests went red.
  • A local CodeQL 2.27.1 run (same query packs as this repository's analysis) reproduced exactly the 7 alerts on c8f7ab9 and reports 0 on this branch. The PR scan below only reports changed lines, so the alerts close when main is rescanned.
  • npm run verify passes; 1198 tests plus one Windows-only skip, 100% coverage; also run on Node 18.20.8 and 22.23.2.
  • Benchmarks against 1.3.0: no measurable slowdown on any path; request-body logging up to 7% faster.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (would cause existing behavior to change)
  • Documentation only
  • Internal / tooling

Checklist

  • npm run build passes
  • npm test passes (100% coverage threshold enforced)
  • npm run lint passes
  • npm run type-check passes
  • npm run format:check passes
  • CHANGELOG.md updated under ## [Unreleased]
  • CLAUDE.md / README.md updated when public API, options, or conventions changed
  • Tests added or updated where appropriate

Related issues

Resolves code-scanning alerts #5, #6, #7, #8, #9, #10 (js/remote-property-injection) and #11 (js/log-injection).

…s error line

Masked copies (request bodies, request and response headers, maskMetaKeys
metadata, Error fields, toJSON() output) were built by assigning each
caller- or client-chosen key onto a fresh object. Assignment consults
Object.prototype, so with Object.prototype frozen a key named after one
of its members (toString, valueOf, ...) threw: a logged request body
became "[UNSERIALIZABLE]", a maskMetaKeys entry degraded to its
_unserializable / _redactionFailed / "[RedactionFailed]" form, and a
mixed-case Constructor header dropped the whole request entry.

- Rebuilds now define their keys (an object spread in redactEntries,
  Object.fromEntries elsewhere), so every key is logged as it is.
  __proto__, constructor and prototype are still left out, and header
  names are checked against them after lowercasing, so a __PROTO__
  header can no longer silently disable a redactPaths header entry.
  Closes the CodeQL js/remote-property-injection alerts in
  src/redact.ts.
- The error-path console.error line writes the method, URL and error
  message JSON-escaped, with Unicode control, format and separator
  characters as \u escapes, so none can split or forge the line.
  Closes the CodeQL js/log-injection alert.
- Output is otherwise byte-identical, with no measurable slowdown.
@codecov

codecov Bot commented Sep 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Hiprax
Hiprax merged commit e9be33e into main Sep 28, 2026
11 checks passed
@Hiprax
Hiprax deleted the fix/define-rebuilt-keys-escape-error-line branch September 28, 2026 17:54
@Hiprax Hiprax mentioned this pull request Sep 28, 2026
9 of 13 tasks
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.

2 participants