It may be possible in some calls to submit a GUID that will retrieve a record even if it is not the record of the requestor, or a record in the same study.
Normally we defend against this by "namespacing" DDB keys with the studyId and or healthCode of a participant. These are not input from the user, but taken from the user's session, so cross study/user requests will fail. But we have not been consistent in this practice.
In addition to auditing that we do this everywhere, we could also implement post-load checks in our DAOs to verify the object loaded matches the caller. The key thing we want to do is avoid "perimeter defense" where we check in controllers and then don't check again anywhere in our code to verify permissions.
However this could be disruptive to our system since we don't pass the caller through the system. In other systems I've worked on, ThreadLocal variables were used to associate user/session info with a call (similar to the way Play is associating the request to the call, I believe). Alternatively, every call in the application could take "participant" like it currently takes "study," but this is tedious.
However, scoping primary keys could get us a long way of the way to where we want to be.
Source: Use AWS WAF to Mitigate OWASP’s Top 10 Web Application Vulnerabilities (July 2017), recommendation A4.