Several things on Tradelynx arrive as a link rather than a login. A quote to look at. A review request. A pre-listing report an agent has prepared for a seller. The link contains a long random code, and holding it is what proves you are the intended reader.
That design is normal and it is reasonable, but it puts a duty on us: the code has to be unguessable, and nobody should be able to sit there trying codes at speed.
What we found
We cap how fast anyone can act on such a link. Accepting a quote, declining a review, uploading a photo to a report: all limited per source, and have been for weeks.
We had not capped how fast anyone could read one.
That is precisely the wrong half to protect. Acting on a link changes one record. Reading one hands over the information, and the information is the thing worth guessing codes for. All four read endpoints were uncapped while all five action endpoints were capped, because the rule got applied to whatever the author was thinking about that day.
The one that mattered most
The pre-listing report. A seller's link returns the property's address, a condition report with costed findings, and time-limited links to photographs of the inside of the home. Anyone holding the link could ask for that as fast as they liked.
All four reads are now capped at a rate far above anything a person opening their own link will reach, and well below anything useful to somebody working through possibilities.
And the record of who read it
While we were in there we found the report's access log could not answer the questions a log exists for. It was not recording which property had been disclosed, even though the identifier was sitting three lines above. It recorded no requester at all. And the list of what had been disclosed left out the two things that make that report sensitive: the costed findings, and the photographs.
That last one matters most, because those categories are what we assemble a subject-access response from. A category we do not declare is information we would not return to somebody who asked what we hold about them.
All three are fixed. The requester is stored as a one-way hash rather than an address, and we are not going to call that anonymous, because a hash of an IP address can be worked backwards. It is enough to spot somebody working through codes, which is what it is for.
Why we are telling you
Because a check written down and applied to half the cases is the kind of thing that stays half-applied until somebody reads it back against the code. There is now a check that refuses to let a new link of this kind ship without a cap, and it has been tested by deliberately breaking it.