What survives

Does obfuscation break typed arrays and binary data?

Binary code looks fragile under protection because it depends on exact byte layout: one shifted offset or one flipped endianness flag and the output is silently wrong rather than loudly broken. Measured against the real engine in five configurations, every byte came out identical, and the reason is structural rather than lucky.

Why binary code looks like it should break

Code that works with bytes has a property most application code does not: it can be wrong without being broken. If a property name is mangled, you get an exception and a stack trace. If a byte offset moves or an endianness flag flips, you get a number. It is the wrong number, and it will travel through the rest of the program looking exactly like a right one.

That makes the stakes feel higher for parsers, codecs, hashing routines, wire-format readers and anything that touches a file header. Teams that ship this kind of code often assume protection is a risk they cannot verify, so they either exclude those files or skip protection altogether.

The measured answer is that this whole family is untouched, and the reason is worth understanding, because it tells you which single option could change the answer.

What was measured

A sample was written to exercise the surface in one file: construction of Uint8Array, Int16Array and Float64Array from literals; the wrapping rules that turn 256 into 0 and 257 into 1; the separate clamping rule that turns 300 into 255 in a Uint8ClampedArray; BYTES_PER_ELEMENT and byteLength; two views over one ArrayBuffer; DataView reads and writes with the endianness flag set both ways; subarray against slice; set with an offset; a view created at a byte offset into the middle of a buffer; and the typed-array forms of map, filter and forEach.

The file was run in Node, then protected and run again, with the two outputs compared line by line. Five configurations were used: the default ES5 target, the modern target, a realistic renaming build with identifier replacement and global renaming, that same build on the modern target, and a string-table build with MoveStrings and EncodeStrings enabled.

All five matched the original exactly. The four bytes DE AD BE EF still read back as deadbeef through a big-endian getUint32 and as efbeadde through the little-endian call on the same buffer. A little-endian setUint16 of 0x1234 still landed as 34 then 12 in the underlying bytes. subarray still shared memory with its parent while slice still copied. A view created at byte offset 4 still reported that offset and saw the write.

Why it holds: index access is not a rename site

Member renaming rewrites a property name in exactly three places: an identifier key in an object literal, a string key in an object literal, and a dotted member access. A computed access with brackets is not one of them.

Binary code is built almost entirely out of the two forms that fall outside that list. Element access is bytes[0], which is computed. The values flowing through it are numbers, and numbers are not names at all. What remains is a set of calls on built-in objects, and by default the member transform is not enabled, so those are untouched too.

Endianness deserves its own sentence, because it is the part people expect to break. The endianness of a DataView call is an ordinary boolean argument, not a name and not a syntactic feature. Protection has no more reason to alter it than it has to alter the number 4. The platform's own byte order is a property of the machine at run time and is not something a source transform participates in.

This is also why the result is stable across configurations. Neither the ES5 rewrite nor the string table introduces a new place where a numeric index or a boolean argument would be treated as a name, so all five builds agree.

The one option that changes the answer

There is a configuration that breaks binary code, and it is worth stating precisely because the fix is a one-line change rather than a decision to skip protection.

If member renaming is switched on with a pattern that matches the names of built-in methods, the calls your binary code makes stop resolving. Measured directly: a regexp written to match subarray, slice, getUint32, setUint16 and byteOffset produced a build that threw a TypeError at the first renamed call it reached, reporting that the method is not a function, because a rename applies to the call site whether or not you own the method being called.

That is the option working as documented rather than a fault in it. A member pattern is a list of names you are claiming; anything it matches gets rewritten. The rule that follows is simple: scope the pattern to names your own objects define, and keep platform vocabulary out of it. In the same sample, a pattern restricted to the object properties the code itself declared produced output identical to the original on both targets.

If you would rather work from an explicit list than a pattern, the members documentation covers custom mappings, and the variable exclusion list covers the identifier side of the same question.

Protection is not encryption for the bytes themselves

A separate belief is worth correcting while we are here: protecting the code that reads a binary format does not protect the format. The two are different things and only one of them is in the file.

If your program parses a licence blob, decodes a model, or unpacks a proprietary container, the layout of that data is discoverable by watching the program use it. The reader can set a breakpoint on the parse, inspect the buffer, and read the fields in order regardless of how the surrounding source is written. Anything shipped to the client is readable by the client, and a format is no exception to that.

What protection changes is the effort required to understand the reading code quickly, and in a string-table build, the effort required to pull magic strings and header signatures out of the file as plain text. That is a real but bounded gain, and it is a different claim from making the data unreadable.

The practical split is the same one that applies everywhere else on the client. Use protection to raise the cost of casual analysis, and keep anything that must remain unknown behind an API call, on a server the reader does not control.

Scope, and how to check your own build

This article covers typed arrays, ArrayBuffer views and DataView, measured on the default configuration and on the four variants named above. It does not cover the iteration protocol for custom binary containers, which turns on how a value is consumed rather than on how bytes are addressed.

Verifying your own build is a short exercise and is more useful than reasoning about it. Write a scratch file that prints the bytes and numbers your format produces at a few checkpoints, protect it with the configuration your project actually uses, run both files under Node, and compare the output. Print hex rather than decimal so a byte-order difference is visible at a glance.

The result to expect is no diff. If one appears, narrow it to a single transform before drawing a conclusion, since the transforms are independent switches. In this family, the first thing to check is whether a member pattern is matching platform method names.

Frequently asked questions

Does obfuscation change byte offsets or alignment in an ArrayBuffer?

No. Offsets are numeric arguments and element access is a computed bracket form, neither of which is a place where renaming rewrites a name. Measured across five configurations, a view created at byte offset 4 still reported that offset and read the same value, and two views over one buffer still shared memory.

Is DataView endianness preserved after protection?

Yes. The endianness flag is an ordinary boolean argument. Measured on the same buffer, a big-endian getUint32 still returned deadbeef and the little-endian call still returned efbeadde, and a little-endian setUint16 still wrote its bytes in the expected order.

Do subarray and slice keep their different semantics?

Yes. Measured, subarray still returned a view sharing memory with its parent, so a write through it was visible in the original, while slice still returned an independent copy. Protection does not alter which built-in method a call resolves to.

Can member renaming break typed-array code?

It can, if the pattern matches built-in method names. A regexp matching subarray, slice, getUint32, setUint16 and byteOffset produced a build that threw a TypeError at the first renamed call it reached, because the rename applies to the call site regardless of who owns the method. A pattern scoped to your own property names produced output identical to the original.

Does protecting a parser protect the binary format it reads?

No. A format shipped to the client can be read by watching the client use it, by pausing on the parse and inspecting the buffer. Protection raises the effort of reading the parsing code quickly; it does not make the data unreadable. Anything that must stay unknown belongs behind an API call.

How do I verify binary behaviour in my own project?

Print your format's values in hex at a few checkpoints, protect the file with your project's own configuration, run the original and the protected version under Node, and diff the two outputs. Hex makes a byte-order difference visible immediately. Expect no difference, and narrow any difference to one transform.

Related reading