Skip to content

Reject prototype keys in validateRideLength (#1089) - #1090

Open
gangster wants to merge 1 commit into
shift-org:mainfrom
gangster:ridelength-validation
Open

Reject prototype keys in validateRideLength (#1089)#1090
gangster wants to merge 1 commit into
shift-org:mainfrom
gangster:ridelength-validation

Conversation

@gangster

@gangster gangster commented Aug 5, 2026

Copy link
Copy Markdown

Fixes #1089.

validateRideLength tested the submitted value with in, which walks the prototype chain, so toString, constructor, hasOwnProperty, valueOf and __proto__ were all accepted as valid ride lengths and stored verbatim. EventDetails.vue renders the field directly, so an event could display "constructor miles".

-    validateRideLength(rideLength) {
-      value = getString(rideLength);
-      return (value in RideLength) ? value : null;
-    },
+    validateRideLength(field) {
+      const value = getString(field);
+      return Object.hasOwn(RideLength, value) ? value : null;
+    },

Three changes in those three lines:

  • Object.hasOwn instead of in, so only the four declared lengths pass.
  • const value — it was previously assigned with no declaration and became an implicit global on every call (globalThis.value === "0-3" after one call). Harmless under non-strict CommonJS, a ReferenceError under ESM or "use strict".
  • Parameter renamed rideLength to field, to match every other validator in the file, which all take a field name rather than a value.

Tests

Three cases added to the existing validator_test.js. Two of them fail before this change — I stashed the source fix to confirm they aren't vacuous:

before after
accepts the known lengths pass pass
rejects anything else fail pass
does not leak a global fail pass
suite 54 pass / 2 fail 56 / 56

End-to-end verification

Unit tests alone did not seem like enough for a validation change, so I restarted the dev server on the patched code and re-ran the same manage_event round trip used to find this, reading each result back through retrieve_event:

posted ridelength HTTP stored before stored after
constructor 200 "constructor" null
__proto__ 200 null
toString 200 null
0-3 200 "0-3" "0-3"
bogus 200 null null

The 0-3 row is the one that matters: prototype keys are rejected without over-rejecting legitimate values. Loading the event page with 0-3 set renders "0-3 miles" as expected, and the prototype-key text is gone.

Not changed

Unlike the other validators, this one silently returns null for an unrecognised value rather than recording an error through errors.addError(field). That may be deliberate — rejecting a bad ride length outright would fail the whole save — so the behaviour is unchanged here. Flagging it in case it is worth revisiting separately.

Object.hasOwn needs Node 16.9+; docker-compose.yml pins node:24.15.0-slim. There is no existing hasOwn/hasOwnProperty usage in the codebase, so happy to switch to Object.prototype.hasOwnProperty.call(...) if you would rather stay conservative.

The ride length check used the 'in' operator, which walks the
prototype chain, so "toString", "constructor", "hasOwnProperty",
"valueOf" and "__proto__" were all accepted as valid ride lengths and
returned unchanged. They persist to the calevent row and EventDetails
renders the field verbatim, so an event could display, for example,
"constructor miles".

Switch to Object.hasOwn so only the four declared lengths pass.

Also declare the local: 'value' was assigned without const/let and so
became an implicit global on every call, which would throw under
strict mode or ESM. Rename the parameter to 'field' to match the other
validators, which all take a field name rather than a value.

Adds three cases to validator_test.js. Two of them fail before this
change.
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.

validateRideLength accepts any Object.prototype key as a ride length

1 participant