Measured Behaviour

Does obfuscation break occupancy exemptions?

Most of what a tolling system computes, it computes itself. An occupancy exemption is different: it is a claim made by the party who benefits from it, and the only thing between the claim and the discount is a comparison against a second source. We measured what happens when the comparison stops running and everything else stays exactly where it was.

A claim, and the one thing standing behind it

We built one peak hour on one managed lane. Seven passages: a genuine three-person carpool declared an hour ahead, a two-person carpool declared in good time, a solo driver who declares nothing, a registered clean vehicle exempt by programme, a genuine two-person carpool declared five minutes before the gantry, a three-person carpool at the peak rate - and one driver who declared three occupants against a roadside count of one.

The policy is ordinary. Two occupants pay half, three or more travel free. A declaration must be set thirty minutes before the gantry, because a driver who can change it in the lane is not declaring anything. A claim above the roadside count is a false declaration and costs $75.00 on top of the full toll.

The correct run collects $103.00: $28.00 in tolls and $75.00 in penalty. Two carpools travel free, one pays half, the clean vehicle is exempt, the late declaration pays in full, and the false claim pays the toll plus the penalty.

Protection alone changed nothing across five profiles. The failures start when member renaming reaches the names on the occupancy configuration object - the mechanics are in Protect Members.

The half that fails open, and the half that fails closed

This object carries two kinds of key and they fail in opposite directions. It is the cleanest instance of that split we have measured.

honourDeclaration and the exemption table itself are PERMISSIONS - they say this occupancy is entitled to a discount. Lose either and the answer to 'is this entitled?' becomes no. Collection rises from $103.00 to $52.00 in tolls with no penalties, every genuine carpool is charged in full, and the complaints arrive the same week. Loud, wrong, and self correcting.

verifySensor is a PROHIBITION - it says refuse if the roadside count disagrees. An unread prohibition permits. Lose that key and the driver who claimed three occupants against a count of one travels FREE: collection falls from $103.00 to $20.00, tolls from $28.00 to $20.00, penalties from $75.00 to $0.00.

The false-declaration counter reads ZERO in that arm, and it is not lying. That counter is computed by the comparison that went missing. A run that cannot compare finds nothing to report, so it reports nothing - and a lane with no false declarations looks exactly like a lane where nobody cheats.

The authority's own rule passed, printing 7 passage(s), 4 exemption(s) granted, 0 false declaration(s), $20.00 collected. Four exemptions where the correct run granted three. Every number in that sentence is accurate.

The union hides the dangerous half

We ran both halves together, which is what a broad rename over the whole configuration object does. The result is $52.00 - byte-identical to losing the permission alone.

That is not a coincidence and it is the single most important measurement in this article. Once no exemption can be granted, there is no claim left to falsely make, so the missing comparison has nothing to compare. The loud half REMOVES THE SUBJECT the quiet half operates on.

The consequence for testing is uncomfortable. The broad, realistic, sloppy pattern produces the loud, obvious, self-correcting failure. The narrow, careful, well-intentioned pattern - the one that spares the exemption table because somebody recognised it as important, and takes the verification flag beside it because nobody did - produces the silent one that lets every false claim through. The dangerous cells are in the middle of the matrix, not at the wide end.

And the rest of the system agrees with itself throughout. In the arm where verification is lost, the exemption table on the configuration screen is complete and correct, the caller-supplied occupancy rule is still reported as installed and in use, and the roadside sensor is still running and still reporting a count. It is simply never consulted.

Two more keys, two more directions

declarationLeadMin is the thirty-minute rule. Lose it and the carpool that declared five minutes before the gantry - inside the lane, after the traffic was visible - is honoured: collection falls from $103.00 to $99.00. A small number that represents the entire difference between a declaration and a decision about the bill.

cleanVehicleExempt goes the other way. Lose it and the registered clean vehicle is charged $8.00 it does not owe: collection rises to $111.00. That one is loud, because the driver holds a certificate and knows it.

falseDeclarationPenaltyUsd is the price counter, and it behaves better here than in most areas we have measured. Lose it and the false declaration is still DETECTED, still printed on the passage line, and the counter still reads 1 - it simply costs $0.00, and the total falls from $103.00 to $28.00. The enforcement record is intact and the deterrent is gone.

The negative control is a lane where everybody is honest

We ran the same seven passages with every driver as a solo traveller who declared nothing and carried no clean-vehicle tag. Collection: $60.00, seven full tolls, zero exemptions, zero false declarations, and every arm in this article completely inert.

Constructed test data is honest. Nobody writes a fixture in which the test driver lies, because the fixture is there to prove the happy path, and a lane on which nothing is ever declared cannot see a verification failure by construction.

The mirror is one field away and it is loud: renaming baseTollUsd or passedMinute produced no readable passage(7 of 7) -- refused before charging. Losing the value fails closed. Losing the check that stands behind a self-declared claim fails open, and the counter that would have told you reads zero.

What we would actually do about it

Phrase the guard as a permission, not a prohibition. A flag that means 'this claim was corroborated' is falsy when it goes missing, and a missing corroboration refuses. A flag that means 'refuse if contradicted' is falsy too, and a missing contradiction permits. The two cost the same at design time and fail in opposite directions.

Reconcile exemptions granted against the sensor's own record, from a different source than the exemption configuration. The authority's rule passed here precisely because it was defined over a counter the missing comparison computes.

Alarm on a false-declaration rate of exactly zero. Every managed lane in the world has some fraud. A counter that reads zero across a whole peak hour is not good news, it is a broken detector, and it reads the same in both cases.

And test the halves, not the union. The union in this file is the safe-looking failure; the single narrow key beside it is the one that lets a false claim through free.

A declaration set in a phone app is a client-side claim in the most literal sense - OWASP MASVS and JavaScript Resilience covers what can and cannot be asserted on the device the claim is made from.

Frequently asked questions

Does obfuscation stop occupancy verification working?

Not on its own. Five profiles - the ES5 target, the modern target, both emit-gate configurations and the string-encoding profile - all produced behaviour identical to the unprotected run: seven passages, $103.00 collected, one false declaration caught.

What did break it?

Member renaming reaching the key that turns on the comparison against the roadside count. The library reads its options by name; a name it cannot find is a key you never supplied, and the vendor default for 'verify against the sensor' is FALSE.

Why does losing that one key fail open rather than closed?

Because of how the flag is phrased. It is a prohibition - refuse if the sensor disagrees - and an unread prohibition permits. The keys beside it are permissions - this occupancy is entitled to a discount - and an unread permission refuses. Same transformation, opposite directions, decided purely by phrasing.

Our false-declaration count is zero. Is that good news?

It is the symptom. That counter is computed by the comparison that went missing, so a run that cannot compare reports nothing and the counter is telling the truth. A lane with no false declarations at all across a peak hour is a broken detector, not an honest population.

Does a broad rename pattern make this worse?

Counter-intuitively, no. Losing the verification flag AND the permission together came back byte-identical to losing the permission alone - $52.00, no exemptions, no false claims - because with no exemptions available there is nothing left to falsely claim. The silent failure only appears under a narrow pattern that spares the exemption table and takes the flag beside it.

What did the free rider actually save?

In the correct run that driver paid $83.00: the $8.00 toll plus a $75.00 penalty. With verification lost they paid $0.00 and were recorded as one of four granted exemptions. Nothing in the output distinguishes them from the two genuine carpools.

How do we protect a managed-lane front end safely?

Scope the member pattern so it cannot reach configuration keys, phrase corroboration flags as permissions, and reconcile granted exemptions against the sensor record rather than the configuration. Protect Members documents how the regexp is applied and what a safe scope looks like.

Related reading