Compatibility
Published
Most option dictionaries carry values: a timeout, a port, a level. The file system's options are different. They carry permission and limits -- whether you are allowed to overwrite, whether a delete may recurse, what gets excluded from a copy. Losing one of those does not corrupt a value. It changes what your program is allowed to do, and the default is always the permissive answer.
What was measured, and who reads the option names
One sample driven through five protection profiles for the base column, and through both member-renaming profiles for everything after. It exercises the operations a build script and a desktop application actually perform: an exclusive create that must not clobber an existing file, an append to a log, a directory listing with typed entries, a recursive delete, a tolerant delete of a path that is not there, an idempotent directory create, a stat read, and a copy with an exclusion filter.
Every option name here is read by node, not by this file. The names are looked up in an object the file authors, which makes the literal a rename site while the reader is a runtime that cannot be rebuilt, patched or redeployed alongside your bundle. That is the same asymmetry as a database driver or a browser API, with one difference: these options gate destructive operations.
The base column is clean. All five profiles reproduced the unprotected output line for line, including the guard verdicts, the entry kind, the delete results and the filtered copy. Protecting a build tool or an installer with a default configuration does not change what it does to disk.
A guard that refuses to overwrite, and then overwrites
The exclusive-create flag is the sharpest result of this pass. Unprotected, the write refused with the expected error and the original file was still on disk. With a pattern matching the option name, the write succeeded, the file was replaced, and the verification line went from confirming the original content to reporting it gone.
The mechanism is one step long. The flag lives in an object literal, the literal was renamed, node looked for the flag it knows and did not find it, and an absent flag means the documented default. The default for a write is create-or-truncate. So the branch that exists specifically to prevent data loss is the branch that is removed, and the operation it was guarding runs and reports success.
The same arm truncated a log. Two writes to the same path, the second one carrying the append flag, produced two lines unprotected and one line after renaming: the append became a replace. Anything that keeps a file open across runs -- a log, an audit trail, a resume-from-here checkpoint -- has this shape, and losing the flag means each run erases the previous one.
Note what does not happen. There is no error, no warning, and no difference in the exit status. The only evidence is the content of a file nobody reads until they need it, which is usually during an incident.
An exclusion list that quietly stops excluding
A copy with a filter is how build scripts keep secrets and logs out of a distributable. With a pattern matching the filter option, the exclusion stopped being applied and the log file that had been filtered out was copied into the destination.
The failure direction matters. A renamed filter does not copy nothing, it copies everything, because a filter that node cannot find means no filtering at all. Every option in this family behaves the same way: the safe behaviour is the one you opted into, so losing the option gives you the unsafe default rather than an error.
That arm also demonstrated a trap that has nothing to do with the file system. The same pattern matched the array method of the same name elsewhere in the file, and the directory-listing code that used it threw a TypeError. A member pattern does not know that one occurrence is an option key and another is a built-in method; it matches names. Short, common option names -- filter, name, type, length, size, source, target -- overlap heavily with the standard library, so a pattern written for a config object can disable a language feature three functions away.
Shapes, limits, and the loud half
The encoding option decides whether you get a string or a binary buffer. With it renamed, the read produced a buffer, the type check changed, and the test for a string method returned false. That is a wrong-type failure that travels: the buffer interpolates into a template without complaint and fails at the first string operation, some distance from the read.
The typed-entries option behaves similarly and fails faster. Renaming it made the directory listing return plain strings instead of entry objects, and the code that asks each entry whether it is a directory threw a TypeError on the first one. Renaming the size property of a stat object, which node writes and this file only reads, produced undefined and turned a size comparison from true to false.
The recursive and force options are the loud pair. Renaming them made the sample fail at its very first statement, during the cleanup that prepares the working directory, with a system error saying the path is a directory. That is the best possible outcome: it happens before anything is written, it is impossible to miss, and it stops the run. Compare it with the overwrite result above, where the same category of option failed silently and destroyed a file.
Why this family behaves worse than other option dictionaries
Option dictionaries have been measured on this site before: the options a child process is spawned with, the options a request is made with, the options a retry helper reads. They all fail the same way mechanically -- the runtime looks for a name it knows, does not find it, and applies its default -- but the defaults are not equally dangerous.
A renamed timeout gives you a longer wait. A renamed encoding gives you the wrong type, which usually throws somewhere. A renamed exclusive-create flag gives you permission to destroy a file, and a renamed exclusion filter gives you a distributable containing whatever you were trying to keep out of it. The failure is not larger, it is just aimed at something that does not come back.
There is a pattern here that is now worth naming as a class. A safety option always defaults to the unsafe behaviour, because the safe behaviour is the one you opted into by writing the option down. That has now been measured for a buffer limit, a dry-run flag, a remote kill switch, a retry ceiling and, in this sample, an overwrite guard and a copy filter. In every case renaming the name removed a restriction rather than corrupting a value.
It also means a smoke test is a poor detector here. A build tool whose output is a directory of files will produce that directory happily: the run succeeds, the artifacts exist, the exit status is zero. The differences are in which file was overwritten, how many lines a log has, and what ended up inside the archive.
What this means in practice
The general rule for member renaming applies here with an extra edge. Any name that is read by something other than your own code -- a runtime, a driver, a library, a file on disk -- must stay out of the pattern. For file system options the consequence of getting it wrong is not a wrong value on a page; it is a file that is gone, a log that no longer accumulates, or a secret that has been copied into a distributable.
Anchor the pattern to a prefix that only your own members use, rather than trying to enumerate the option names to avoid. The enumeration approach fails on exactly the names that are easy to forget, and the collision arm above shows that even a correct list of option names can catch unrelated built-in methods of the same name.
Verify the destructive paths specifically. Run the protected build against a directory containing a file that must not be overwritten, then check the file rather than the exit status: in the measured run the operation reported success while replacing it. Do the same for one append and one filtered copy. Three assertions cover the whole family.
If you need renaming across a build tool, keep the file system layer thin and behind your own functions, so the option literals live in a handful of places you can review rather than scattered across the codebase.
Frequently asked questions
Does obfuscation break file system operations in Node?
Not in the default configuration. A sample performing an exclusive create, an append, a typed directory listing, a recursive delete, a tolerant delete, an idempotent directory create, a stat read and a filtered copy produced identical output on all five protection profiles measured. The file system only becomes a surface when member renaming matches the option names node reads.
Can member renaming cause data loss?
Yes, and it was measured. A write carrying the exclusive-create flag refused to clobber an existing file unprotected; with the flag name renamed the write succeeded and replaced the file, because an absent flag means the documented create-or-truncate default. Nothing was thrown and the exit status did not change.
Why did my log file stop accumulating after obfuscation?
Because the append flag was renamed, so node did not see it and used its default, which replaces the file. The measured sample went from two lines to one. Anything that reopens a file across runs -- a log, an audit trail, a checkpoint -- has this shape, and each run erases what the previous one wrote.
Why does my build now ship files the copy filter used to exclude?
Because a filter node cannot find means no filtering. Renaming the filter option copied the excluded log file into the destination. Options in this family always fail toward the permissive default, since the restrictive behaviour is the one you opted into by naming it.
Can a member pattern break built-in methods with the same name?
Yes, and one arm showed it directly. A pattern written for an option name also matched the array method of the same name elsewhere in the file, and the directory-listing code that called it threw a TypeError. Short option names overlap heavily with the standard library, so the pattern must be anchored to your own naming convention rather than to a list of option names.
Do file system failures under renaming show up as errors?
Some do. Renaming the recursive and force options failed at the first statement with a system error naming the path, which is the ideal outcome. The overwrite, append and filter results produced no error at all, so the two halves of this family need different verification: one is caught by a smoke test, the other only by checking the files.
How do I verify a protected build tool?
Run it against a directory holding a file that must not be overwritten and inspect the file rather than the exit status, since the measured run reported success while replacing it. Add one append assertion and one filtered-copy assertion. Those three cover the destructive options, and they must run against the protected artifact, not the source.
Related reading