fix: add mass-assignment protection to all PATCH/PUT endpoints - #1
Draft
meetdarc-tech wants to merge 1 commit into
Draft
fix: add mass-assignment protection to all PATCH/PUT endpoints#1meetdarc-tech wants to merge 1 commit into
meetdarc-tech wants to merge 1 commit into
Conversation
Wire validatePayloadFields into the global middleware chain so routes registered in ROUTE_ALLOWED_FIELDS actively reject unknown fields (400 UNKNOWN_FIELDS) instead of silently ignoring them. Add explicit validateSchema declarations to every mutable-resource endpoint that previously relied only on manual destructuring: - wallet.js: updateWalletLabelSchema, updateWalletSchema, updateHomeDomainSchema, updateWalletLimitsSchema, updateLeaderboardVisibilitySchema; fix the broken inflationDestinationSchema (was a no-op JSON Schema object, now strips unknown fields correctly) - stream.js: updateScheduleSchema for PATCH /schedules/:id - admin/webhooks.js: updateWebhookStatusSchema for PATCH /:id (schema enum replaces manual inline status validation) - admin/geoRules.js: updateGeoRuleSchema for PATCH /:id - admin/pledges.js: cancelPledgeSchema for PATCH /:id/cancel Each schema is the single source of truth for that resource's updatable field set. validateSchema strips unknown keys before the handler runs, so protected fields (role, status, apiKeyId, createdAt, verified, publicKey) are never seen by service or database layers. Add tests/security/mass-assignment.test.js with two suites: 1. validatePayloadFields rejection: protected fields on registered routes receive 400 UNKNOWN_FIELDS. 2. validateSchema stripping: protected fields are removed from req.body before reaching the handler, verified by echoing the processed body.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds explicit mass-assignment protection across all mutable-resource PATCH/PUT endpoints. Prior to this change, endpoints relied on manual field destructuring to avoid persisting unknown fields — safe by accident, not by design. This change makes the protection deliberate, centralized, and testable.
Problem
Two gaps existed:
validatePayloadFieldswas never wired into the app. The middleware and itsROUTE_ALLOWED_FIELDSallowlist existed insrc/middleware/validation.jsbut was only used in tests — never mounted inapplyMiddleware(). Unknown fields were silently accepted for all routes.Several PATCH/PUT endpoints had no schema declaration. They extracted fields safely via destructuring, but there was no single authoritative list of what fields each resource accepts. The
inflationDestinationSchemawas a silent no-op (a plain JSON Schema object passed to a function expecting the custom format).Changes
src/bootstrap/middleware.jsMount
validatePayloadFieldsas global middleware (after body parsers, before routes). Routes registered inROUTE_ALLOWED_FIELDSnow actively reject requests with unknown fields —400 UNKNOWN_FIELDS.src/routes/wallet.jsSix schema middleware declarations added and applied:
PATCH /:id/inflation-destinationPATCH /:id/labelupdateWalletLabelSchema—{ label }PATCH /:idupdateWalletSchema—{ label, ownerName }— redundant denylistpublicKeycheck removedPATCH /:id/home-domain+PUT /:id/home-domainupdateHomeDomainSchema—{ domain, sourceSecret }PATCH /:id/limitsupdateWalletLimitsSchema—{ daily_limit, monthly_limit, per_transaction_limit }PATCH /:id/leaderboard-visibilityupdateLeaderboardVisibilitySchema—{ visible }src/routes/stream.jsupdateScheduleSchema({ amount, frequency }) added toPATCH /schedules/:id.src/routes/admin/webhooks.jsupdateWebhookStatusSchema({ status: enum['active','disabled'] }) added toPATCH /:id. Schema enum validation replaces the previous manual inline check.src/routes/admin/geoRules.jsupdateGeoRuleSchema({ active, description }) added toPATCH /:id.src/routes/admin/pledges.jscancelPledgeSchema({ reason }) added toPATCH /:id/cancel.tests/security/mass-assignment.test.js(new)Layer 1 — validatePayloadFields (rejection):
PATCH /wallets/:idandPATCH /donations/:id/statusreturn400 UNKNOWN_FIELDSfor every field in the protected set:status,role,apiKeyId,createdAt,verified,publicKey.Layer 2 — validateSchema (stripping):
For each endpoint above, sends a valid payload mixed with protected fields and asserts the processed
req.bodyat the handler contains only the declared allowed fields.How protection works
Two complementary layers built on existing infrastructure:
Acceptance criteria
validateSchemabody fields.status,role,apiKeyId,createdAt,verified,publicKey) cannot be set by clients — rejected with400or stripped before the handler runs.Testing
npm test tests/security/mass-assignment.test.js