Review one operation from packet to persistent state
This is a defensive source-review exercise, not an exploit or a drop-in patch for every branch. Pick one handler you own, such as moving an item, buying from an NPC or accepting a trade. Follow the actual code from packet decoding to inventory/currency changes and persistence.

1. Separate decoding from permission
First establish that the complete packet is available and fields are decoded within bounds. Then establish that the authenticated character is allowed to perform the requested operation in the current state. A valid packet length does not prove item ownership. A valid item ID does not prove the sender owns that item.

Build a review table:
Input field       Server-side authority/check
Source slot       existing inventory container and valid bounds
Item identifier   resolve owned item; do not trust a client object reference
Count             positive, representable, no greater than owned stack
Destination       allowed container/slot and current occupancy
Price             derive from authoritative server/shop data
Currency          sufficient balance and representable result
Operation state   reject conflicting exchange/storage/shop transitions

Adapt these checks to your actual handler; do not add client-controlled price as a new protocol field just to follow this table.

2. Check arithmetic before mutation
Review signed/unsigned conversions and narrowing casts. For positive count and unit price, test whether multiplication fits the chosen result type before computing a total. Check balance before subtraction. Check destination stack capacity before addition. Checking a value after it has already overflowed is too late. Numeric limits must come from the actual protocol, database column and gameplay rules, not an arbitrary constant copied from another system.

3. Define the operation's invariant
For an item move, the total count across source and destination should stay constant. For a purchase, the charged amount and granted count should correspond to the same accepted operation. If persistence fails midway, know which changes are committed, rolled back or retried. Do not assume a database transaction automatically makes separate in-memory inventories consistent. Check the architecture's locking/serialization and retry behaviour before adding new concurrency controls.

4. Add a bounded staging test matrix
Test a normal operation, zero count, negative values where the decoder permits them, maximum representable values, source larger than owned stack, full destination, stale item state, insufficient currency and disconnect/reconnect around completion. Repeat a valid operation quickly within a controlled local test and verify conservation of items/currency. Invalid operations should leave state unchanged and produce a bounded diagnostic, not a process crash or unbounded log growth.

Do not send malformed packets to public servers. Use unit tests or a local harness for your handler, then an authorised staging client. Store fixtures without real account tokens or player data.

5. Report evidence at the correct level
Record handler/revision, invariant, test inputs, expected state and actual state. A unit test of arithmetic is not a complete test of networking, concurrent actions or crash recovery. A client-side guard improves UX but is not the server's security boundary.

Reference and follow-up
OWASP's input-validation guidance distinguishes syntactic from semantic validation and recommends server-side enforcement. The inventory review table and test matrix here are M2City's application of those principles, not a claim that OWASP audited a Metin2 branch. No third-party server was tested. Share a small redacted handler fragment and a violated invariant for discussion.