The profile page lets people change
three things.
Their name.
Their email.
Their phone number.
The endpoint behind it
is four lines long.
It takes the body of the request,
loads the user,
copies every field in the body
onto the user,
and saves.
It was quick to write,
and it means you never
have to touch the endpoint again
when the form grows.
Now open the network tab,
copy that request,
and add one more field.
Role, admin.
Or the account's credit limit.
Or the id of the company
the user belongs to.
Or the flag that says
their email was verified.
Send it.
On a surprising number of systems
the save goes through,
because the code copied
every field it was given,
and the user record
happens to have a field
with that name.
The form never showed it.
The form was never the limit.
The form is a suggestion
your own page makes
to a browser you do not control.
Anyone can skip the page
and write the request by hand,
with whatever keys
they can guess.
And guessing is easy.
The names are in your public docs,
in the responses your API
already sends back,
in the field names
of the script that runs your page.
Nothing in the logs looks wrong.
It is one ordinary update request
from a logged in user
editing their own profile.
The mistake is not
a missing check on the role field.
It is that the endpoint
never said
what it accepts.
It accepted whatever
the database happened to hold.
So turn it around.
Each endpoint gets its own
small input type
with exactly the fields
that action is allowed to change,
and nothing else.
The profile update
has name, email and phone.
The role lives in a different endpoint,
behind a different permission.
Reject unknown fields
instead of ignoring them,
so a probe shows up in the logs
as an error you can count.
Never pass the request body
straight into the thing you save.
And add a test
that sends the extra field
and checks the record did not change.
The fields a user may edit
are a decision.
Write that decision down
in the code,
not in the form.
– Serguey Asael Shinder
Top comments (0)