Skip to content

fix: persist sanitized query and route params - #610

Open
Nwoyemartha wants to merge 2 commits into
Heliobond:mainfrom
Nwoyemartha:fix/sanitize-query-and-route-params
Open

fix: persist sanitized query and route params#610
Nwoyemartha wants to merge 2 commits into
Heliobond:mainfrom
Nwoyemartha:fix/sanitize-query-and-route-params

Conversation

@Nwoyemartha

Copy link
Copy Markdown
Contributor

Summary

sanitizeInputs() validated req.query and req.params but discarded the value returned by sanitizeString(): HTML tags were detected/processed, yet the stripped value was never persisted for query and route parameters (only req.body received its sanitized result). This fix writes the sanitized value back for all three input sources.

Implementation notes (verified against Express 5.2.1 / @types/express@5):

  • Query: Express 5 exposes req.query as a getter-only prototype property that re-parses the URL on every access, so per-key writes to the returned object are lost and a whole-object req.query = ... assignment throws TypeError in strict mode. The middleware now builds the sanitized copy and defines it as the request's own query property (Object.defineProperty), which cleanly shadows the getter — no unsafe casts, and the value persists for all downstream handlers.
  • Route params: req.params is a plain own property, so per-key assignment (req.params[key] = sanitized) is type-safe and persists wherever params are populated when the middleware runs. Note that Express 5 assigns req.params at route-layer dispatch, so at a pre-route app.use position params are empty (this is a runtime lifecycle fact of Express, not of this change); when attached on a parameterised route, sanitized values persist.

Changes

  • Persisted sanitized query parameter values (sanitized copy defined onto req, shadowing the Express 5 getter)
  • Persisted sanitized route parameter values (per-key assignment, no casts)
  • Added regression coverage in src/__tests__/sanitize.test.ts
  • Preserved existing validation behavior (SQL injection / command injection / path traversal still throw 400 ApiErrors; non-string query values untouched)

Tests

Added src/__tests__/sanitize.test.ts (11 tests, following the supertest conventions of csrf.test.ts):

  • sanitizes req.body as before (strips tags, keeps inner text)
  • sanitizes a query parameter containing HTML/script tags and writes the stripped result back (asserts the handler sees the stripped value — i.e. persistence, not just validation)
  • sanitizes a route parameter containing HTML/script tags and writes the stripped result back (route-attached middleware, where Express has populated params)
  • persists the sanitized param value on req.params via direct middleware invocation
  • still rejects 1' OR '1'='1 / \whoami` / ../../etc/passwd with a 400 for dangerous input in a query parameter` (SQL injection, command injection, path traversal)
  • still rejects a route parameter containing an SQL injection pattern
  • leaves non-string query values (arrays) unchanged, leaves a plain query value with no HTML unchanged, does not mutate req.body when it is absent

Commands run (actual results):

  • npx jest src/__tests__/sanitize.test.ts — ✅ 11 passed
  • npx prettier --check on both changed files — ✅ pass (repo's lint-staged pre-commit hook also ran prettier --write + eslint --fix on commit)
  • npx eslint src/middleware/sanitize.ts src/__tests__/sanitize.test.ts — ✅ clean
  • npx eslint src/ — 2 errors / 173 warnings; identical count on main (pre-existing, in unrelated files config.test.ts, process-exit-codes.test.ts)
  • npx tsc --noEmit — fails only in pre-existing, unrelated files (registry.ts, admin.ts, batch.ts); verified identical error set with changes stashed on main
  • npx tsc -p tsconfig.test.json --noEmit — 21 error lines on both main and this branch; none in the changed files
  • npx jest (full suite) — 23 failing suites; verified the failure set is identical on main (diff of FAIL lists matches, apart from timing suffixes), so all failures are pre-existing and unrelated to this change

Security / correctness

The change ensures all three input sources — body, query, and route params — receive the same sanitization transformation. HTML/script tags are now actually stripped from query and route parameter values instead of only being validated, while existing rejection of dangerous input patterns (SQL injection, command injection, path traversal) and non-string handling are unchanged.

Compatibility

Behavior changes only in the intended security direction: query/route values that previously kept their HTML content after passing through sanitizeInputs are now stripped of tags (e.g. ?search=<b>x</b>yxy), matching what already happened for req.body. No API shape changes: response bodies, routes, and status codes are unaffected; validation rejections behave exactly as before.

Fixes #518

sanitizeInputs() validated query and route params but discarded the
value returned by sanitizeString(), so HTML tags were detected yet never
stripped from req.query/req.params (issue Heliobond#518).

- Persist sanitized query values by defining the sanitized copy as the
  request's own `query` property, shadowing Express 5's getter-only
  req.query (whole-object assignment throws in strict mode and per-key
  writes to the re-parsed object are lost).
- Persist sanitized route param values via per-key assignment, which
  works wherever params are already populated.
- Add regression coverage: body/query/route-param sanitization at the
  middleware integration boundary, rejection of SQL injection, command
  injection, and path traversal patterns preserved, and non-string
  query values unchanged.

Fixes Heliobond#518

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@drips-wave

drips-wave Bot commented Sep 2, 2026

Copy link
Copy Markdown

@Nwoyemartha 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

Lockfile had drifted from package.json: it still contained semantic-release,
@octokit, @actions, and pnpm helper packages that are no longer declared, and
was missing the root "license" field. Regenerated with npm to restore a
consistent install state.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
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.

sanitizeInputs middleware discards the sanitized value for query and route params — HTML tags are not stripped there

1 participant