Hi Ricardo Fernandez Serrata,
Indeed I could have left out the the empty replacement argument to which replaceAll(...) defaults to. But as I tend to refer to my own writings to complement documentation, specifying all arguments in full, including those which could also be omitted, gives me a better grip on argument structure of functions.

The "filter" part may be a tad obscure. I'd have commented the reason if there was any reasonable in-flow documentation feature.

Your first question you have already answered yourself: "If filter = 0, the truthiness of the whole expression will be the same as contains(), if filter = 1, the truthiness will be inverted". Exactly this defines the difference between whitelist and blacklist: using a whitelist results in taking the "yes" branch if package is member of the array, and "no" otherwise, the blacklist has the opposite effect. The case of >1 won't occur: The loop tests existence of two files: whitelist (index 0) and blacklist (index 1)

"filter = index, always" - no, not always. The "For each" block has two exit points, one while still looping, and one when loop limit has been reached. We can have 3 cases:
whitelist exists,
blacklist exists (but no whitelist),
neither exists.
The 4th case, both white- and blacklist exist, can be ignored, because covered by case "whitelist exists" already.

First two cases, either white- or blacklist exist, are dealt with the "continue loop" branch. But notice that it doesn't always lead back to "For each" - instead it only branches back to "For each" if the file tested for wasn't found. If the file was found, a "wild" loop exit occurs, through the "Does file exist" yes-branch.

I'll put the resulting effect on index and filter variables into the table below, which should clarify what happens:

condition:       | index | filter  | contains()
whitelist exists |   0   |   0     | straight
blacklist exists |   1   |   1     | inverted
neither exists   | null  |   1     | inverted

The case of "neither exists" is equivalent to blacklist case without any entry.

The detail you might have missed is that variable index is only assigned within the loop, while its last value is used outside of the loop in the "contains()" block. But as the "For each" block could have taken the exit through the "OK" path, thereby unassigning the index variable, its last value is preserved in the filter variable. I could have assigned variable index=1 in the OK path instead of the filter=index assignment in the DO path, and used index instead of filter in the contains() block, but not have gained anything by this.

You might have guessed already that my forum post relating to missing logical XOR was related to this flow. In this case, by limiting inputs to 0 or 1 anyway, logical XOR was neither needed nor did I have to work around it, as I was able to use bitwise XOR instead.

Thank you for subjecting my flows to such detailed scrutinizing. Such feedback on possibilities to improve them I regard highly. It is a great boon of the community shared flows to not only share functionality, but also hints geared towards aiding improvement of one's work.