How to build a client-side validation API
-
So, a recent change made Creep.getActiveBodyparts non-configurable. Presumably, this was to prevent people from being able to exploit bugs like the claimController one. I'd like to open up a discussion about the role of client-side validation versus server-side.
I propose that client-side validations should be advisory only and we should be allowed to "break" the implementation of these as we wish. In this particular case, this method can be overridden to be more performant by using a cache of the body parts. If you don't choose to make the validations advisory (but rather mandatory), you will reach one of two outcomes.
The first option is to prevent configurability on all methods that may need to be called from any intent validation code. If you do this as a blanket block on the prototype object you effectively prevent prototype extension and if you do it on an ad hoc basis you will repeatedly break user code with each new modification. These options are undesirable because prototype extension is one of the primary mechanisms for adding high-level behavior to creeps.
The second option is to discontinue using the prototype chain for client-side validation. This would basically change all calls in the form of this.getActiveBodyParts() to calls in the form of storedGetActiveBodyParts.call(this). This would prevent any prototype modifications from affecting the validation code. This option is undesirable because overriding prototype methods to be more efficient is currently a viable strategy in the game. It would be a shame to force players to effectively "open source" some of their proprietary performance enhancements.
My proposal is to treat client-side validation as advisory/optional. If I choose to bypass certain validation checks, it should be a waste of my CPU because the actual intent processor should disallow the intent from going through. Taken to the extreme, you could even publish unvalidated methods like `Creep.unsafe_claimController` which would completely bypass validations and not return any error code. This could reduce CPU usage a hair for players who are really grinding on it.
What are everyone's thoughts on this?
-
For what it's worth, here are 3 ways that you can still bypass the client-side validation for claimController.
creep.body = [{ type: "CLAIM", hits: 100 }]; creep.claimController(creep.room.controller);
creep.body.push({ type: "CLAIM", hits: 100 }); creep.claimController(creep.room.controller);
creep.body[0].type = "CLAIM"; creep.claimController(creep.room.controller);
The purpose of showing these is to highlight the difficulty in locking down the prototype object. For body, you have to freeze the property definition, the body part array itself, and all of the objects within the body array. This extends across exposed object in the game, and just feels like a losing proposition compared to the sure-fire way of preventing cheating, which is to validate outside of the client sandbox.
-
Fully behind you on this one. Client side validations should only be there to help and direct the user. There should always be strong validations on the processor side.
-
Agree, and I can't think of any reason not to (besides the effort for the devs to implement all of this)
To balance it cpu-wise, I would propose that if user code bypasses "userland" checks and then fails the intent in the server layer, it could be penalized with a large constant cost.
-
There are strong server-side validations. That recent change wasn’t a security fix (the security fix was this one), it’s due to the need of being sure in underlying code behavior consistency.
This would basically change all calls in the form of this.getActiveBodyParts() to calls in the form of storedGetActiveBodyParts.call(this).
This is exactly the case now, see this commit.
My proposal is to treat client-side validation as advisory/optional.
We may consider adding an experimental option “Disable all client-side checks” in the future, when we introduce the script runtime options feature.