Claims and benefits

Does obfuscation break policy coverage limits?

No, not on its own. We protected a policy-limits module on five presets and every loss settled at the same figure. Then we renamed the members it reads, and a flood loss on a policy that excludes flood was paid $25,000 - by an exclusion list that was still configured, still enabled and still running.

What we actually measured

A claims platform decides how many dollars a loss is paid. The module we built has the shape those platforms really have: an exclusion list, a table of category sublimits, a per-occurrence limit, an annual aggregate, and a coverage verifier the carrier wired in because policy status lives in their system and not the vendor's.

The engine that consumes all of that is a separate installed library, copied in unprotected exactly as a real dependency arrives. We protected only the file the team owns.

Baseline first: five presets, every loss through every build, twenty-five comparisons, twenty-five identical outputs. Protection alone changed nothing about what the policy paid.

An exclusion list can be lost from either end

The exclusion list is a rule written as data. It is a list of strings - flood, wear - matched against a field on the loss record called perilCode. That gives it two independent ways to stop working, and we measured both.

Rename the option carrying the list and the vendor default is substituted: an empty list, because a library cannot guess what your policy excludes. Rename the field the list is matched against and the list survives intact while matching nothing.

Both arms produced the same outcome. A $40,000 flood loss on a policy that plainly excludes flood was paid:

flood-is-excluded PAID=true settled(... claimed $40000 paid $25000.00 ...)

It came out at $25,000 rather than $40,000 only because the per-occurrence limit was still working and trimmed it. The exclusion contributed nothing.

The two arms differ in one respect that matters for how you would find this. In the first, the configuration dump changes and says so - exclusions=(none - nothing is excluded). In the second, the dump still lists both exclusions correctly. The rule is configured, enabled, running, and matching nothing, because renaming rewrites names in code and never the text a rule is written in.

A sublimit that is still visible in the configuration

The sublimit table is a lookup: jewelry is capped at $5,000, electronics at $3,000. We renamed a single key inside that table.

sublimits=_0x1:$5000 electronics:$3000

The $5,000 cap is still there. You can see it in the dump. The electronics cap beside it is untouched and works perfectly. But a $14,000 jewelry theft, which should have been trimmed to $5,000, was paid in full - the lookup missed, and a missing sublimit is not an error, it just means the category is not capped.

A one-name rename removed the cap from exactly one category and left the configuration looking populated. This is the same shape we saw in the rating tables of an underwriting book, and it is the reason a configuration screen is weak evidence: it shows you that entries exist, not that anything finds them.

Two ceilings that trim, and both of them failed open

The per-occurrence limit and the annual aggregate both work by trimming a payment down. Rename either and the trim is skipped.

The per-occurrence limit gone: a $31,000 storm loss paid at $31,000 instead of $25,000. The aggregate gone: a late-year fire paid $22,000 in full, and the policy year closed at $70,000 against a $60,000 aggregate - $10,000 of cover that does not exist was issued, and nothing in the run says so.

Both fail open, which is not a general law about ceilings. It follows from what each true branch guards. When the true branch of a comparison is the restrictive one, skipping it is permissive. When the true branch is the permissive one, skipping it is restrictive - and that is exactly what happened to the coverage verifier below.

The refusal whose reason says the opposite

The carrier's coverage verifier hands back a permission: a field called inForce. Rename it and the library reads undefined, treats the missing permission as a refusal, and prints the guard's own explanation for approving:

fire-paid-in-full PAID=false not-in-force(policy-p-3311-in-force)

Read that line twice. The claim was refused for not being in force, and the stated reason is that the policy is in force. Every loss in the file was refused this way, including the ones that should have been.

That is loud, and loud is survivable - your call centre finds it the same morning. It is also the clearest triage signature in this whole series: a refusal whose reason asserts the opposite outcome almost always means a field on a return shape has moved. This is the seventh consecutive area we have measured where that signature appeared.

The flag in front of the verifier makes it quiet again

The verifier sits behind an enabling flag. We measured the flag alone, the verifier alone, and both.

The union's behaviour is byte-identical to the flag alone. Losing the flag means the verifier is never consulted, so losing the verifier as well changes nothing - the loud failure is switched off by the quiet one. Worse, in the flag arm the configuration still reports:

verifier=caller-supplied(checks the policy status of record)

which is an accurate answer to the wrong question. The verifier is present, bound and correct. It is simply never called. We measured this same shape in five separate modules this pass, and it held in all five.

What actually protects you

Write rules as allowlists where you can. A denylist that stops matching pays claims it should refuse; an allowlist that stops matching refuses claims it should pay, which someone reports within the day. That is a shape argument, not a scope one, and it survives more than renaming.

Phrase the flag a callback hands back as a permission rather than a prohibition. The coverage verifier here failed closed precisely because inForce is a permission and a missing permission is falsy. Had it been called excluded, the same rename would have paid every claim silently.

And reconcile the ledger. An annual aggregate is checkable against the payments that fed it. A single nightly assertion that the year-to-date total does not exceed the aggregate would have caught the $70,000 in this article on the day it happened.

Frequently asked questions

Does protecting a claims platform change what a policy pays?

Not in what we measured. Five presets covering the ES5 and modern targets plus the string transforms, every loss through every build: twenty-five comparisons, twenty-five identical settlements. The default protection path changed nothing.

Why did an exclusion list stop excluding anything?

Two independent ways, and we measured both. Renaming the option carrying the list substitutes the vendor's empty default. Renaming the record field the list is matched against leaves the list intact and matching nothing. Both paid a $40,000 flood loss on a policy that excludes flood.

Can we tell from the configuration screen that something is wrong?

Not reliably. When we renamed a single key inside the sublimit table, the dump still showed a $5,000 cap - under a new name - while the $14,000 claim it governs was paid in full. A configuration screen shows that entries exist, not that anything finds them.

Why did the ceilings fail open but the coverage check fail closed?

It follows from what each comparison's true branch guards, not from what the value means. The per-occurrence and aggregate limits have a true branch that trims a payment, so skipping it pays more. The coverage verifier's permission flag guards approval, so a missing permission refuses.

What does a refusal that cites the opposite reason usually mean?

A field on a callback's return shape has moved. We saw a claim refused for not being in force with the stated reason that the policy is in force. It is the clearest triage signature in this series and it has now appeared in seven consecutive areas we have measured.

Does defence in depth help here?

Only if the layers live where one change cannot reach both. The coverage verifier sits behind an enabling flag, and losing the flag made losing the verifier irrelevant - the union behaved exactly like the flag alone, while the configuration still reported the verifier as present and bound.

What is the cheapest safeguard to add?

Reconcile the running total against the payments that produced it. An annual aggregate is arithmetic you can assert nightly, and it would have caught the $70,000 paid against a $60,000 aggregate in this article on the day it happened.

Related reading