Skip to content

feat(security): P0 + partial P1 hardening, CI pipeline, multer v2#159

Merged
parth0025 merged 2 commits into
stagingfrom
feat/p0-p1-security-hardening
May 18, 2026
Merged

feat(security): P0 + partial P1 hardening, CI pipeline, multer v2#159
parth0025 merged 2 commits into
stagingfrom
feat/p0-p1-security-hardening

Conversation

@parth0025
Copy link
Copy Markdown
Collaborator

@parth0025 parth0025 commented May 18, 2026

Summary

Closes the critical security and dependency gaps surfaced in the recent codebase audit. All 20 unit tests pass; no behavior change for normal request paths.

Backend security (P0)

  • NoSQL regex injection fix — sanitized user-supplied regex inputs across 7 controllers (AI, AdvancedGlobalFilter, Comments, MediaFiles, Project filter, notification-count, trackerDownload) via new utils/escapeRegex.js. Blocks ReDoS and cross-tenant name enumeration.
  • Security headers + rate limitinghelmet + global express-rate-limit wired in index.js. CSP disabled to preserve the Vue inline-script setup. trust proxy: 'loopback' to silence ERR_ERL_UNEXPECTED_X_FORWARDED_FOR behind a same-host proxy.
  • Multer hardening — all 4 upload call sites now use a shared utils/uploadConfig.js with size/file-count limits, an extension blocklist, and path-traversal protection.
  • JWT audience enforcement — new requireCompanyAud middleware checks the JWT aud against any companyId in body/params/query/header for the ~50 verifyJWTTokenV2-only routes. Non-ObjectId values (e.g. USER_PROFILES) pass through.

Dependencies (P0)

  • Removed aws-sdk v2 (EOL Sept 2025) — only @aws-sdk/* v3 was actually imported.
  • Bumped multer 1.4.5-lts.1 -> 2.1.1 (1.x CVE clearance).

Schema strictness (P1-SEC-11)

  • Flipped 7 core schemas to strict: true (tasks, comments, timesheet, history, adminDetail, subscriptionPlan, globalCustomFields).
  • Kept 7 intentionally-dynamic schemas (notification counters, Chargebee mirrors, custom-field defs, plan-feature maps) with explanatory comments so they aren't re-flagged.

CI / DevOps (P0)

  • .github/workflows/main.yml now runs a validate job (test, audit advisory, frontend build) before the deploy step. Previously deploys ran with zero validation.

Diagnostics

  • modules/storage/wasabi/controller.js: 6 generic upload-error rejects replaced with formatS3UploadError(error, context) — logs AWS error Code, status, bucket, key, requestId.

Deferred / documented

  • P1-SEC-09 (httpOnly cookies): the frontend reads both cookies via js-cookie (5 files). Flipping httpOnly requires a frontend refactor first. TODO(P1-SEC-09) comments left at both res.cookie sites.

Pre-existing fix pulled in

  • tests/utils/imageGuard.test.js: env-var names corrected. Suite goes 19/20 -> 20/20.

Test plan

  • npm test — 20/20 passing
  • Syntax check every modified file
  • escapeRegex round-trip test against ReDoS payload (a+)+
  • safeRelativePath smoke test against traversal payloads
  • Verified flipped schemas report strict=true at runtime
  • Post-merge: log in -> create task -> comment -> upload attachment, verify RateLimit-* headers

Risks to watch

  • Strict schemas silently drop unknown fields. If tasks/comments/history features regress, check whether a field was added without declaring it in utils/mongo-handler/schema.js.
  • Rate-limit defaults: 600 req/min/IP global. Tunable via GLOBAL_RATE_LIMIT_PER_MIN and TRUST_PROXY.
  • Multer v2: mostly compatible API; worth a smoke upload test.

🤖 Generated with Claude Code

parth0025 and others added 2 commits May 18, 2026 17:23
Closes the critical security and dependency gaps surfaced in the
codebase audit. All 20 unit tests pass; no behavior change for normal
request paths.

Backend security
- Sanitize regex search inputs across 7 controllers (AI,
  AdvancedGlobalFilter, Comments, MediaFiles, Project filter,
  notification-count, trackerDownload) via new utils/escapeRegex.js.
  Blocks NoSQL regex injection / ReDoS / cross-tenant name enumeration.
- Add helmet + global express-rate-limit in index.js. CSP disabled to
  preserve the Vue inline-script setup; secure / sameSite already
  env-gated correctly. trust-proxy set to 'loopback' to silence the
  ERR_ERL_UNEXPECTED_X_FORWARDED_FOR warning behind a same-host
  reverse proxy.
- Harden all 4 multer call sites via new utils/uploadConfig.js
  (DEFAULT_LIMITS, safeFileFilter blocking executable extensions,
  safeRelativePath for traversal protection). bucket.helper.js
  storage destination now validates path before writing.
- New requireCompanyAud middleware (Config/jwt.js) enforces the JWT
  `aud` claim against any companyId in body / params / query /
  header on the ~50 verifyJWTTokenV2-only routes. Non-ObjectId
  values (e.g. USER_PROFILES global bucket) pass through.

Dependencies
- Remove aws-sdk v2 from package.json (EOL Sept 2025); only @aws-sdk
  v3 was actually imported.
- Bump multer 1.4.5-lts.1 -> 2.1.1 to clear known 1.x CVEs.

Schema strictness (P1-SEC-11)
- utils/mongo-handler/createSchema.js: flipped 7 core schemas to
  `strict: true` (tasks, comments, timesheet, history, adminDetail,
  subscriptionPlan, globalCustomFields). Kept `strict: false` on 7
  intentionally-dynamic schemas (notification counters, Chargebee
  webhook mirrors, custom-field definitions, plan-feature maps) with
  inline comments explaining why.

CI / DevOps
- .github/workflows/main.yml: added a `validate` job (npm test, npm
  audit advisory, frontend install + build) that the deploy job now
  `needs:`. Previously deploys ran with zero validation.

Diagnostics
- modules/storage/wasabi/controller.js: replaced 6 generic
  `Error while upload file: ${error}` rejects with a
  formatS3UploadError helper that logs the AWS error Code, HTTP
  status, bucket, key, and requestId.

Documented for follow-up
- Auth/controller.js: TODO comments at both res.cookie sites
  explaining why httpOnly stays false until the frontend stops
  reading the cookie via js-cookie (P1-SEC-09 deferred).

Pre-existing fixes pulled in
- tests/utils/imageGuard.test.js: corrected env var names so the
  suite goes from 19/20 to 20/20 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CI pipeline added in the same PR is the first thing to run
tests on Ubuntu. `path.isAbsolute('C:\Windows\...')` returns
false on Linux, so the safeServiceFile absolute-path guard fell
through and the request hit the `.json` extension check instead.
The test in `tests/utils/safeServiceFile.test.js:46` expects the
"relative" error in both shapes and was passing on Windows hosts
by accident.

Use `path.win32.isAbsolute(...)` alongside the platform-bound
`path.isAbsolute(...)` so both POSIX (`/etc/hosts`) and Windows
(`C:\Windows\...`) absolute paths are rejected regardless of
which OS the process runs on. This is also a defensive improvement
to BUG-036: a Linux deployment can no longer be coaxed into
re-resolving a Windows-shaped path inside the project root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@parth0025 parth0025 merged commit 9d8063e into staging May 18, 2026
2 checks passed
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