Measured Behaviour

Does obfuscation break short sale locate rules?

Two lists sit on the same options object. Both are arrays of symbols, both are read the same way, and the library defaults both to the same empty array. Rename either one and the library substitutes that empty array. One of them fails closed and stops the desk within a minute; the other fails open and stops nothing except a regulatory clock.

Two lists, one default, one rename shape

The fixture is a trading desk's locate screen over forty-eight orders. Short sales need stock located first. Five deep names are on an easy-to-borrow list and may be sold short without a specific borrow; two names are on the regulator's threshold list and an ageing fail in either must be closed out inside thirteen days; bona fide market making is exempt from the locate; a hard borrow costs 450 basis points.

The correct run accepts all forty-eight orders, refuses none, accepts no naked short, runs three close-out clocks with none breached, and accrues 42,210.00 in borrow fees.

This page exists because of a prediction written down before the arms were run. An earlier measurement on this site concluded that an allowlist and a denylist both fail open when the option name carrying the list moves, on the reasoning that a list the library cannot read is indistinguishable from one you never supplied. That was measured against a library whose missing-list default was no restriction. This library's missing-list default is the empty set, and the empty set means opposite things depending on what the list enumerates.

Protection with default settings, on all five profiles, was byte-identical.

The allowlist fails closed, and everybody knows within a minute

easyToBorrowList enumerates permissions: these symbols may be shorted without a specific borrow. Empty means no symbol qualifies.

Renaming it refused twelve orders. Accepted orders fell from forty-eight to thirty-six and the shorted quantity from 65,400 to 19,400. This is a desk refusing its own ordinary flow in the five most liquid names on its book.

That failure is not dangerous. It is expensive and immediate: traders call, orders get rejected, somebody rolls back the deployment before lunch. The same direction showed up on exemptStrategies - also an allowlist of permissions - which refused the two market-making orders.

The denylist fails open, and nothing anywhere says so

thresholdSecurities enumerates prohibitions: these symbols carry a mandatory close-out obligation. Empty means no symbol is barred.

Renaming it changed exactly one number on the report. Accepted stayed at forty-eight, refused at zero, naked shorts at zero, shorted quantity at 65,400, borrow fees at 42,210.00 - and CLOSE-OUTS-REQUIRED went from three to zero.

Three ageing fails in threshold securities stopped having a clock at all. Not a breached clock, which would be a finding: no clock. The compliance rule in this fixture refuses any threshold fail past its close-out, and it passed, because a fail with no clock cannot be past one.

Same object, same empty vendor default, same rename, opposite directions. The determinant is not whether the thing is a list and not what the vendor chose as a default - both are the empty array - but whether the list enumerates what is allowed or what is forbidden. Losing a set of permissions removes permissions. Losing a set of prohibitions removes prohibitions.

The union is loud, and that is the problem

Renaming both lists together - which is what a single broad pattern over an options object does - produced the allowlist's twelve refusals and the denylist's zero close-out clocks.

So the combined arm is loud. Somebody notices it immediately, fixes the refusals, and ships. The close-out clocks are still off and nothing in the incident points at them, because the symptom everyone chased was the refusals.

A loud failure sharing a cause with a silent one is worse than either alone, because the loud one supplies a plausible complete explanation. When you fix a noisy regression caused by a renaming pattern, enumerate every name that pattern reached, not just the one that was screaming.

The control that switched off a requirement and changed nothing

^(requireLocate)$ reverts the entire locate requirement to the library's false: short sales are accepted with no borrow at all.

It was byte-identical to the correct run. Every figure, including the naked-short counter, was unchanged.

That is not a defect in the measurement; it is the finding. Every order on this session had a valid locate anyway, so removing the requirement removed nothing that was being used. The control becomes observable only on the first order that needs it - which, on a compliant desk, may be weeks away and will look like an ordinary order at the time.

^(closeOutDays)$ was inert for the same kind of reason: reverting thirteen days to the library's thirty-five matters only if some fail is between the two, and none was. Both are honest negatives about this data, not about the engine.

What the record side did

Renaming symbol on the order records reached both list failures at once - twelve refusals and zero close-out clocks - because a symbol that cannot be read matches neither list.

^(side)$ was the quietest of the record arms. No order could be recognised as a short sale, so the shorted quantity read zero, the close-out clocks read zero, borrow fees read 0.00, and the screen accepted all forty-eight orders with the compliance rule passing. The whole short book became invisible to the control designed to screen it.

^(borrowConfirmed)$ refused the four threshold shorts that were relying on a specific borrow, and ^(failAgeDays)$ turned the three close-out clocks off while leaving every other figure identical. ^(qty)$ and ^(priceUsd)$ went non-finite and the screen refused before doing anything.

The long-only session sees none of it

The same file runs the same desk on a session with no short sales in it. Every arm on this page is inert there: neither list, neither clock and neither fee decides anything.

A desk sampled on a long-only day is structurally unable to see this class, and so is a test fixture built from one.

What to do about it

Protection alone is not the risk - the base column is clean on all five profiles. The exposure is RenameMembers reaching option keys and record fields that an installed compliance library reads. Scope MemberRegexp accordingly; Protect Members documents how the pattern is applied.

The design point is worth stating separately, because it is true whether or not you obfuscate anything. Prefer to express a control as an allowlist of permissions rather than a denylist of prohibitions, because a permission list that goes missing removes permissions and somebody complains, while a prohibition list that goes missing removes prohibitions and nobody does.

If the desk runs its own stack rather than a vendor's, protecting a self-hosted JavaScript application covers the deployment side of this.

Where a denylist is unavoidable - a threshold securities list is, because the regulator publishes it - assert its contents before you use it. A list that arrives empty should stop the desk, not quietly describe a market in which nothing is restricted.

Frequently asked questions

Does obfuscation stop a short sale locate from being required?

Not through protection alone; all five profiles were byte-identical. Renaming the option that carries the locate requirement did switch it off, but on a session where every order had a valid locate the output did not change at all - which is what makes that particular failure hard to catch.

Why did the two lists fail in opposite directions?

Because of what they enumerate. The easy-to-borrow list is a set of permissions, so losing it removes permissions and twelve ordinary orders were refused. The threshold securities list is a set of prohibitions, so losing it removes prohibitions and three mandatory close-out clocks silently stopped. Both reverted to the same empty array.

Isn't an allowlist supposed to fail closed?

Only against an unknown entry. Against a lost option name it depends entirely on what the library substitutes. Here the substitute is the empty set, which is restrictive for permissions and permissive for prohibitions. A library whose missing-list default means no restriction would fail open on both.

What happened when both lists were renamed together?

Both effects appeared: twelve refusals and zero close-out clocks. The refusals are the visible symptom, so an incident response that chases them finds a complete-looking explanation and leaves the clocks off. A loud failure sharing a cause with a silent one is worse than either alone.

Which record field was the quietest?

The order side. With it renamed, no order could be recognised as a short sale, so the shorted quantity, the close-out clocks and the borrow fees all read zero while all forty-eight orders were accepted and the compliance rule passed.

How should a compliance list be validated?

Assert its contents before use rather than trusting that it arrived. An empty threshold list should stop the desk, because it is indistinguishable from a market in which nothing is restricted, and the library cannot tell the difference on your behalf.

Were the ES5 and modern targets different?

No. Both were measured on every arm in this area and agreed throughout, and the base column with no member renaming matched the unprotected run on both targets plus the two emit-gate profiles and the string-encoding profile.

Related reading