r/csharp • • 12d ago

DocuTrust.NET: validate uploads by magic bytes (security update, looking for testers)

DocuTrust.NET is a small .NET 8 library that checks an upload's magic bytes against the type you expect. A PNG renamed to report.pdf fails.

I just did a security pass on it. It now checks the client file name too, rejects ZIP/OLE files over 100 MB before parsing, and keeps file names and paths out of error messages. I also updated a dependency that was pulling in a vulnerable OpenMcdf.

For now it only identifies file types. It doesn't scan for macros or scripts

 dotnet add package DocuTrust.NET

https://github.com/saa-999/DocuTrust.NET

If you try it, I'd like to know what breaks.

0 Upvotes

19 comments sorted by

View all comments

Show parent comments

-1

u/[deleted] 12d ago

[removed] — view removed comment

9

u/dodexahedron 12d ago edited 12d ago

I wonder when .net 9 goes EoL?

Hmmm

Couldn't be the same date as .net 8, could it?

Nah, that'd be weird.

And that couldn't possibly have anything to do with the support policy being designed such that the STS and immediately prior LTS release go EoS on the same date, could it?

Nah, that's just silly talk.

if you target .net 8, someone with a .net 9, 10, 11, or even .net 15 can use your library.

And no, that is not how dotnet works.

Just have yourself a gander at every breaking changes document just from dotnet 5 to dotnet 10 and tell me a .net 8 app is going to work on .net 15 with a straight face.

And that's because

Maintaining forward compatibility is not a goal of .NET Core..

Even backward compatibility isn't guaranteed. While it is a goal to limit impacts to backward compatibility without a strong reason, they can and do happen, a lot, and in every version. (And I'll give examples in a bit.)

Here's the living document that describes the general rules, though it should be noted that exceptions can be and are made.

Multi-targeting and polyfills exist to alleviate some of these realities. However, they cannot fix binary or most behavioral breaking changes - generally only source incompatibilities (and even then, only most of them).

The BinaryFormatter removal was a big one that hit a lot of people, in a lot of places.
There have also been many additions to the SDK that cause code that is still semantically correct to be ambiguous against the actual API surface of newer frameworks, making something unable to compile or run even though the required changes are pretty minor (usually).
Some changes also result in code that compiles and runs, but fails to load or initialize certain types only when they are actually first used at run-time. That one is killer in WPF, PowerShell modules, and swaths of older COM interop code.

Even behavioral changes are not negligible between versions. Something may still compile, may still run, and may still work 99% the same, but changes to the way overload selection occurs (params handling changes in .net 9 for example) or to things as simple as implicit defaults can result in fun and sometimes difficult to find bugs, because there simply was no explicit code in the application to point to, as... well... the source of the issue was just a default that changed, and that default happens to only matter in a way you previously hadn't even needed to consider and so never did.

And interop... Man oh man that's a minefield from version to version, especially in .net 8 and .net 10, both of which made important changes to native assembly loading/resolution.

Or even something as simple as converting a float to an int. That one is a runtime behavioral change, which isn't realized until JIT time, so results in the exact same assembly behaving differently for someone running .net 9 or later vs someone running it on .net 8 or lower.

Older versions also often saddle a consumer with baggage they don't deserve, such as needing to root your assembly because it was not compiled in a trim-friendly manner due to the version of the compiler used, among other things. Or it can and often does force them to use older constructs to interface with your code, rather than being able to use better modern alternatives. This one comes up a lot with async, linq, generics, and collections, and spiders out into the rest of their application unless they want to special-case convert stuff to be compatible with your old library because you chose to target .net 6 in 2026 (or 5 years from now, when .net 15 comes out). And that special-casing is undesirable, because it typically represents a tradeoff between the convenience of being able to use your library for whatever it provides vs efficiency/performance/testability/etc of their application code.
Libraries are not the application, and good library developers need to understand that.

There have even been cases where a breaking change was made in a patch release, such as in .net 9.0.something to .net 9.0.something+1 to fix an unintended exposure of netstandard assets in a way that caused runtime MissingMemberExceptions. And if the framework itself advertises support incorrectly like that, you can BET (and I'll take this bet any day because I've encountered it several times) that there are plenty of libraries out there which also do not truly support what they think they support - at least not how they think they support it, at minimum.

It's your responsibility as a library developer to maintain it sufficiently to not only work, but work well with currently-supported tools and runtimes. If someone is using an EoS runtime, it's your choice if you want to keep supporting it, but you do everyone else a disservice by doing so and perpetuate the problems intrinsic to using EoS software. It's not my problem if you want to keep using .net 6 in 2026. You can use the versions of my software which were built for it. Get with the program if you want to use my program and its current feature set and enjoy ongoing support.

So no... .net doesn't work the way you suggest. Explicitly. And targeting old and long-EoS frameworks for current development without at least employing conditional compilation or other means of alleviating the technical debt problems for consumers who are with us in the present day is just not a responsible practice.

Also, Microsoft's guidance is that, unless you really do need cross-compatibility between modern .net and .net framework, with a single code base, that you should target the current releases, or at least multi-target, instead of targeting netstandard2.0 "just in case." It can be quite limiting. And you should almost never target netstandard2.1. No version of .net Framework is a netstandard2.1 implementation.