Measured, not asserted

Does obfuscation break container weight verification?

A verified gross mass is not a calculation, it is a condition of loading: under SOLAS, no VGM means the box does not go on the ship. Conditions like that always sit behind an enabling flag in software, and this test renamed the flag and the check that would have caught it in one pattern. The result was a container with no verified gross mass at all reading LOADED, with both weight limits passing it, because a comparison against a number that cannot be read is false in either direction.

A condition, not a calculation

SOLAS chapter VI regulation 2 is unusually blunt for a safety instrument. The shipper is responsible for providing a verified gross mass, and a packed container without one shall not be loaded. There is no tolerance for a missing figure and no degraded mode. Everything else at the gate is arithmetic around that condition: does the verified figure agree with what the shipper declared, is the payload inside the container's rating, was the weighing done by an accepted method, is the declaration signed.

The fixture used here is a terminal gate configured the way a careful operator would configure it: a VGM is required, a signatory is required, declared and verified may differ by at most 500kg, the payload rating is 26,000kg, and two weighing methods are accepted. Four containers are gated, each wrong on at most one axis so the axes cannot mask each other. One clean box loads. One arrives with no verified gross mass at all. One was declared at 21,000kg and weighed 24,200kg. One is a 27,800kg payload against a 26,000kg rating.

The gate engine is copied in unprotected. It enforces whatever the terminal tells it to and has no view about what a container ought to weigh -- which is exactly right for a library, and exactly why its defaults are all permissive: no VGM required, no signatory required, an infinite tolerance, an infinite payload rating.

Protection on its own moved nothing

Twenty-five comparisons across five areas and five presets -- the ES5 default path, the modern target, the emit gate's option set on both targets, and the string transforms -- every protected run identical to the unprotected one, with the reproducibility guard running each original twice before anything was compared to it.

Everything below required member renaming that reached names shared with the installed gate engine.

The flag in front of the condition is the dangerous name

Renaming requireVgm on its own did not produce the bad outcome, and the reason is worth reading before the reason it eventually did. With the SOLAS condition switched off, the container carrying no verified gross mass reached the terminal's OWN rule of record -- an arithmetic check that the VGM equals tare plus declared cargo -- and that rule refused it: container-rejected(mass-check(the container declares no mass)). The box stayed on the quay. A second control, written by the operator rather than the vendor, caught what the first one stopped enforcing.

Now add one more name to the same pattern. requireVgm and requireVgmCheck are adjacent lines in the same options object and any realistic rename pattern that reaches one reaches the other. Together, the output was:

gate(MSKU2000024 vgm=undefinedkg declared=22000kg diff=NaNkg payload=NaNkg [] => LOADED, unchecked)

A container with no verified gross mass was loaded. Note what the reason list contains: nothing. The tolerance comparison did not fire, because the difference is NaN and NaN > 500 is false. The payload comparison did not fire either, for the same reason. Two independent weight limits, both configured correctly, both silent -- not because they were switched off but because an unreadable number cannot exceed anything.

This is the shape a previous pass named: a guard that fails loudly is only loud if it is still reached. The half that would have screamed is disabled by the half that fails quietly, and the pattern that reaches both is narrower than the pattern most people ship.

Each limit alone lets exactly its own container through

The two ceilings were also measured on their own, and they behave as a reviewer would hope until they do not. With the tolerance renamed, it reverted to the library's infinity and the box declared at 21,000kg but weighed at 24,200kg read LOADED with the 3,200kg discrepancy still printed on the line. With the payload rating renamed, the 27,800kg box read LOADED. Renaming both loaded both.

The record fields underneath those ceilings are more interesting, because they reach the same outcome without touching the configuration at all. Renaming declaredGrossKg made the difference NaN and loaded the 3,200kg discrepancy. Renaming tareKg made the payload NaN and loaded the overweight box. In both cases the tolerance and the rating are still displayed correctly on the configuration screen at 500kg and 26,000kg. The limits are configured, present and enforcing nothing.

That is the same finding an earlier pass made about fraud scores, in a domain where the consequence is a container stack rather than a chargeback: a numeric field that loses its name does not become zero, it becomes incomparable, and a ceiling test against an incomparable value fails open.

The terminal's own rule printed a true sentence about a NaN payload

The tareKg arm is the sharpest single result in this area, because the operator's own check was running, was correct, and reported success while the engine's payload was NaN.

The gate line read payload=NaNkg [] => LOADED, checked by mass-check(VGM 31600kg agrees with tare+cargo 31600kg). Both halves are true. The mass check reads the tare from the container record and compares it to the VGM; both the write and the read live inside the protected bundle, so they moved together and the check kept working perfectly. The engine's read of the same field did not move, so the engine saw undefined and its payload arithmetic produced NaN.

One field, two readers, one on each side of the boundary, and only the reader outside the bundle lost it. Any assertion you write about your own data is measuring your own side. It is not evidence about what the library saw.

What failed loudly

The subject of the condition failed closed on every box. With verifiedGrossKg renamed, all four containers read no-vgm(... has no verified gross mass => NOT LOADED), including the clean one -- the terminal stops, and stops completely, within minutes. Renaming the container identifier produced unidentified-container(refused at the gate) four times. Renaming the weighing method left every box carrying method=undefined, which is not on the accepted list, so nothing loaded. Renaming the signatory field left every VGM unsigned and nothing loaded.

That is the contrast this series keeps measuring and it held for the fifteenth area running: losing a VALUE fails closed and loud, losing a MEANING -- a flag, a limit, a unit, a key -- fails open and quiet. The loud failures are the ones your staging environment finds. The quiet ones ship.

What this means if you protect terminal or shipper-facing JavaScript

Treat the enabling flags as a single blast radius with the controls they enable. If requireVgm and requireVgmCheck can be lost by the same pattern, then you do not have two controls, you have one -- and it is the quieter of the two that decides the outcome. The mitigation is placement, not count: a second check only helps if it lives where the transformation cannot reach it, which in practice means the terminal operating system rather than the browser.

Then make the numbers refuse to be compared when they are not finite. Requiring a mass to be finite before testing it against a limit is one line, costs nothing, and turns every open arm in this article into a refusal. It is worth having regardless of obfuscation, because a truncated EDI message and a hand-edited configuration file produce exactly the same undefined.

And assert the gate decision, not the gate configuration -- the distinction an assessor draws in ISO 27001, SOC 2 and your front-end JavaScript between a control that is configured and a control that is evidenced. A smoke test that pushes the four containers through and expects one LOADED and three NOT LOADED catches everything here. A test that reads back the tolerance passes in the arms that matter.

Frequently asked questions

Did obfuscation change any gate decision on its own?

No. Twenty-five comparisons across five areas and five presets were byte-identical to the unprotected runs. Every failure below required member renaming that reached property names the installed gate engine also reads.

What was the worst measured outcome?

A container with no verified gross mass at all reading LOADED, with an empty reason list. That required renaming the SOLAS condition and the terminal's own backstop check in one pattern -- two adjacent lines in the same options object.

Why did the two weight limits not catch the missing VGM?

Because both comparisons were against NaN. The difference between a missing verified mass and a declared mass is NaN, the payload derived from it is NaN, and NaN is neither greater than 500 nor greater than 26,000. An unreadable number cannot exceed a limit, so both ceilings fell silent while remaining correctly configured.

Does renaming the SOLAS condition on its own load an unweighed box?

Not in this fixture. The terminal's own mass check caught it and refused the container. That is the point of the result: the dangerous pattern is the one that reaches the flag and the backstop together, and any realistic member regexp that matches one matches the other.

Which names failed safely?

The verified gross mass itself, the container identifier, the weighing method and the signatory. Each one refused every container including the clean one, which stops the terminal within minutes. The quiet names are the flags, the two ceilings, and the record fields those ceilings are computed from.

Can I trust my own check's success message?

Only about your own side of the boundary. In one arm the operator's mass check printed that the VGM agreed with tare plus cargo while the engine's payload was NaN, because the check's read and the record's write both moved together inside the bundle and the engine's read did not.

What is the cheapest mitigation?

Require a mass to be finite before comparing it to any limit, and treat non-finite as a refusal. That single line converts every open arm here into a loud one, and it is worth having anyway -- a truncated EDI message produces the same undefined that renaming did.

Related reading