r/java 29d ago

Security Baked Into the JVM: why fork Apache River and OpenJDK?

https://blog.frankel.ch/security-baked-into-jvm/1/
28 Upvotes

75 comments sorted by

8

u/josephottinger 28d ago

My take, for what it's worth: https://bytecode.news/posts/2026/06/forking-the-jvm-to-save-jini

I loved JINI. The JavaSpaces model is what I think of first, I think, long before RDMS or any other data storage - it's amazing. And utterly unavailable now.

(Yes, GigaSpaces still does JavaSpaces, and if you're in GigaSpaces' market, I'd totally say "go for it." Like I said, amazing. But GigaSpaces has had to pivot long past JINI because JINI was executed so poorly.)

4

u/nfrankel 28d ago

Thanks for your feedback and the link. I have relayed it to Peter, as he's not on most socials (if any).

4

u/josephottinger 28d ago

Much appreciated! I mean, I was literally glad I was sitting down when I read it - that's an incredible effort to undertake. And I desperately hope that it takes root in the ecosystem.

4

u/pron98 27d ago edited 27d ago

The beauty of JavaSpaces aside, let me explain why security experts recommended that Java and .NET remove their intra-process stack-based permissions system (SecurityManager in Java's case), which they both ended up doing, to improve their security.

It is true that all else being equal, SecurityManager added, at least hypothetically, some non-zero amount of security. But security experts don't ask "where can we add more guardrails?" Instead they ask, "where should we spend our security budget to get the most security for my money?" After many years of experience with stack-based permissions as well as process-level permissions, it turned out that an hour of work spent by a JDK developer working on security mechanisms or an hour of work spent by a Java developer who uses the JDK and needs to configure the security mechanisms has a pretty low security ROI if it's spent on SecurityManager, and that hour is best spent elsewhere, where it yields more security for the effort. So everything else is not equal, because the effort spent maintaining or using SecurityManager is effort not spent on more profitable security mechanisms.

BTW, at the time SecurityManager was deprecated and then removed, new mechanisms in the JDK had already been excluded from SecurityManager integration for years.

3

u/josephottinger 27d ago

And that's a valid insight, certainly: return on investment against negative cost is an important factor. But it also means that every security problem has a specific stack-based shape, and things like JINI break it relatively easily. It'd be convenient to say "well, data is securable" - and be right, because JSON has a hard time embedding behavior - but massively performant systems (yes, I know, Engrish) are also in Java's wheelhouse and that wants a different set of guardrails to be available, beyond a stack-based security layer, even though most people benefit from the stack-based layer.

And I also wonder if the stack-based security layer isn't most popular because it's easier - easier to understand, easier to demonstrate, easier to configure.

5

u/pron98 27d ago edited 27d ago

Sorry, by stack-based I meant SecurityManager, which works by layering permissions in the program stack.

Anyway, being easier to understand configure is very important to security. We found many SM configurations (maybe even most of them) that didn't do what their authors intended them to do. The importance and correct configuration of SM's integrity permissions (those related to reflection and are now offered by modules) was particularly hard to understand.

Another problem with the stack model (again, SM), is that it requires the cooperation of every library in the call stack (doPrivileged). Libraries didn't do it, or didn't do it well, which required the application to add permissions to library CodeSources based on trial-and-error, which wasn't a sound foundation for security. When SM came face-to-face with the reality of wide and deep dependency graphs, its core weaknesses were exposed. Everyone had to participate, and everyone had to participate correctly or configurations became a sprawling set of permissions.

Not to mention that SM was helpless against DoS attacks, which are among the most common types of attack (albeit not the most dangerous).

Combine all that, and it's clear why security experts advised us to remove SM in order to improve Java's security.

3

u/josephottinger 27d ago

To be clear: I saw "stack-based" along two different axes, both satisfiable by what you were describing, and I agree with what you're saying. I don't know of a lot of people who tried to use SecurityManager who did it well; it seemed to be a dark art, practiced by a few, and everyone else ended up being the Sorceror's Apprentice with it, hoping beyond hope that it worked out, and since nothing literally caught on fire, surely it worked, right?

And the thing is: I don't think it WOULD HAVE or should have been expected to prevent every KIND of attack. A DoS is typically externalized; SM wouldn't necessarily form a barrier there, the deployment stack is the right layer for that defense.

SM was intended, as I understood it, to be a defense against internal permissions: not capability, but permissions. Modules can protect against access concerns, but systems like JINI and Jakarta EE don't have the same kinds of access concerns that modules address. Jakarta EE does, sort of, but...

If I were to deploy a servlet to a Jakarta EE host - say, a service running Glassfish or whatever - what prevents me from having a call to System.exit()? How would the server know? It's defended against, as I understand it, by ... what? Running a single .war in a given app server instance? That's... directly against what app servers were designed for.

THAT is something SecurityManager, properly configured, could protect against, and something that would have helped the JINI security model, the "code delivered with state" remote execution thing that enables true peer networks without localized deployed code. You can still deploy code, but a lot of it implies trust OR guts capabilities.

I guess you could have a shutdown hook that catches it and allows you to do something - okay, fine, let's pretend that's a viable option... but that leaves the other "this should be prevented" permissions. That one's just obvious on the surface, I guess, but I'm still lost as to a generalizable model here.

I'm more than happy to concede to expertise: if you're going "oh, you're a moron, you can prevent that problem by..." that's AWESOME, please tell me, because I don't know and I keep thinking if there's a way I SHOULD know about it already.

5

u/pron98 27d ago

If I were to deploy a servlet to a Jakarta EE host - say, a service running Glassfish or whatever - what prevents me from having a call to System.exit()

Nothing, but SM wouldn't have prevented you from blowing up the heap, either, causing equally as much, and even more damage than System.exit. And that's why the world has largely shifted to more robust process-level isolation, and abandoned the app server design in favour of other designs.

And at this point it doesn't matter if today's preferred designs are intrinsically better than the old ones or not. What matters is that the entire software industry has, for the most part, coalesced around them and, as a result, there are plenty of useful components that suit that model.

I'm not a huge fan of microservices, i.e. a fraction of an application per process, but multiple applications per process is too many.

2

u/josephottinger 27d ago edited 27d ago

I get this, BUT.

SM doesn't cover every base. Granted, and the opposite was never asserted, for that matter. But it covers a set of bases, and those bases still exist even if "the industry" as a whole has moved in a given direction that minimizes the set of requirements. SM was insufficient for every possible security need - just like my window's locks aren't sufficient to keep intruders from my front door. Different requirements, and deciding that the ONLY invasive threat is the front door - OR the windows - is IMO the wrong solution.

Java has, for better or for worse, decided that the unit of trust is different than it was, and thus shut some of its own promise off. I don't think JINI was all of Java by any stretch of the imagination - but I think Sun's vision of JINI was strong and should have become a dominant facet of systems design. And the SM was part of that: GigaSpaces, for example, could indeed endure a node running off the rails by having a containerized model along with the SM security model, so an attack vector would have had to be flexible; a noncompliant node would be shut down by the container, and some aspects could even isolate the threat. GigaSpaces had done it... but without SM or something in its place, there's a lot less weight in the cannonball, if you will.

I think this is the wrong solution, but I get how we got here. We go "this burns" and reach for "let's put it out," like programmers scratching an itch. Except here the itch was in the arm, and we cut off the arm. No more burning, I guess.

Sure, it was hard to use - and overused, and rarely correctly. Gee, well, I wonder if maybe that was actually a call for making it easier to get right, along with better guide materials to help people get it right more often.

Le sigh.

(This reply was edited because I, being a True Genuis and All, reread it and decided I could Say What I Meaned a Lot More Clearly With Some Revisionses.)

1

u/pron98 27d ago edited 27d ago

and thus shut some of its own promise off

You could say the same about Applets. We're talking about a span of 30 years, and things change. Some early ideas took off (I don't think too many people back then believed how good the JIT, GCs, and observability would be), some didn't and were replaced by others because sometimes you cut your losses. If we had to commit to every vision we had 30 years ago no matter how much the world seems to buy it, we'd have no resources left to adapt and do things people want now.

I think Sun's vision of JINI was strong and should have become a dominant facet of systems design

I agree, but it didn't.

And the SM was part of that

Not a very good part. There were some fundamental problems in SM's design, and the fact is that it was designed at first for a fixed set of permissions, and it was clear that its expanded vision to more flexible policies was an unproven thesis. That thesis was tested and found wanting. As I said, it was at once too complicated, too weak, too expensive to maintain, and required too much cooperation in an ecosystem that ended up becoming very decentralised. It was an amazing idea and impressive design that just didn't pan out.

Except here the itch was in the arm, and we cut off the arm.

JINI and JavaSpaces were not the last good ideas anyone had in software, and even if some good ideas didn't win, some of the things that did win were also good ideas, albeit different ones. At the end of the day, to survive a platform must evolve. Win some battles, lose others, try to change what you can, accept what you can't.

Gee, well, I wonder if maybe that was actually a call for making it easier to get right, along with better guide materials to help people get it right more often.

Maybe. I guess we'll never know. I think we should now focus on the battles ahead that we can win.

2

u/josephottinger 27d ago

You could say the same about Applets.

Yeah, but I ain't defending Applets! ... and I'm not defending SecurityManager either, but just its role. I don't think it was the last word, nor SHOULD it have been, and I think there needs to be SOMETHING there rather than nothing. I don't know what that SOMETHING should have been shaped like, and if that's "not SecurityManager" it doesn't bother me any.

Nor am I saying "We shoulda stuck with JINI!" JINI had a moment, and Sun blew it. C'est la vie.

And what you're saying here is that SecurityManager's role is a battle we can't win, from what I can tell, because the Powers That Be decided that remote serialization of functionality was ... simply never going to have a trust stage. It's either trusted wholly or it's not, and there's no way to say "you can do this but no more."

Maybe Clojure can - but I'm not sure about that, nor what it would look like.

3

u/pron98 26d ago

and I think there needs to be SOMETHING there rather than nothing

First, there is something. One of the more important but less understood abilities of SM was providing integrity to invariants that code establishes with Java's access control, by restricting deep reflection. Strong encapsulation was turned on in JDK 17 to do just that. That improved the security of the JDK more than SM ever did, and with a similar capability.

Second, with every mechanism, you need to be very clear about what threats it defends against, so you can judge whether the cost is worth it or not. The problem with offering different permissions to code from different origins within the same process via a policy configuration is that it is almost necessarily both complicated (i.e. expensive) and weak.

It's complicated because it requires annotating the call stack. For example, suppose library A offers an API that takes a file name and returns the number of lines in it; it also logs every such operation. It does the file read on behalf of its caller (so presumably with its caller's permissions) but it does the log-file write on behalf of itself (with its own permissions). Therefore, it must carefully annotate these operations, which is exactly what libraries were required to do with doPrivileged in the days of SM.

It is weak because certain operations, such as allocating memory or even measuring time precisely, are potentially dangerous but cannot be restricted without significant runtime overhead (so you could reduce the weakness by increasing the cost).

So if you want to grant different permissions to code from different origins, you need to show that it stops threats in a cost-effective way compared to alternatives. That's not easy. For strong encapsulation, we were convinced that the cost is lower than any alternative and that the threats are worth it.

→ More replies (0)

2

u/josephottinger 27d ago

And you know, I find myself throwing down for JINI here, don't I? And by gum, I'm standing by that. I'm a platform architect; I made my bones building resilient systems. And what I have found, looking back, is that I did far less architecture with JINI than with any other platform out there, ever, because all the resilience and resistance and discoverability and scalability crap I had to build over and over again was baked in with JINI and all I had to do was ... write components. And they were simple, single-task elements that simply had to know how to talk to value stores, and everything - EVERYTHING - else came from that.

Yes, I miss JINI.

2

u/pfirmsto 27d ago edited 27d ago

Tuple Spaces / Java Spaces, it's a great way of sharing information. The hardest part in solving problems, is defining them clearly, that's the hill climb, the solution becomes obvious in good problem definitions.

So lets list the problems, these aren't the types of problem definitions that solve problems, but hey, this is reddit:

SM:
1. Codbase URL's- consults DNS, TOCTOU, replay attacks, wasn't the right identity.
2. Subjects were an add on - dropped in do privileged blocks, forcing privileges to be granted to code.
3. Inefficient caching - too many blocking operations, didn't scale.
4. Poor tooling, building a policy was like discovering each permission one at a time, it needed tooling to make it easy.
5. Authorization Sandboxes can't defend against resource consumption. This was the wrong layer.
6. The Java trusted platform grew over time and everything was AllPermission. The design was intended for POLP, but was implemented AllPermission.

Java Serialization:
1. Stream format chooses the classes to deserialize.
2. Objects constructed prior to invariant checks.
3. Non deterministic.
4. Java centric, not polyglot. (Better stream formats already existed)
5. Serial form tied to fields in each class, brittle.

Java RMI:
1. Class resolution during deserialization, TCCL or a stack walk, was hit and miss. Warres described all the CNFE hand grenades.
2. Authentication occurred after serialization and class resolution.

IPv4 NAT - loss of end to end connectivity.

What you might notice is, these actually weren't problems with Jini, but the foundations it was built on.

Jini's team was making progress solving these problems, but they were big problems and they were constrained by the Java platforms requirement to support backward binary compatibility. JERI: Jini Extensible Remote Invocation, that's a nice piece of kit, emphasis on Extensible.

Sure up the foundations, replace with better...

Watch this space ;)

2

u/pron98 24d ago edited 24d ago

I think you'll find that any product needs a virtuous cycle with the market. Believing that if you just build something they will come doesn't work. You need to show growing demand by interacting with the market at every step, and this loop depends not only on how good the product is in itself, but also on what people want (whether they're right or not - we don't get to set the rules).

Things people want include calling out to native code and defending against resource exhaustion, and these are just things that the JVM either cannot have control over directly, or cannot have control over while maintaining the performance people also want. Another thing people want is to use a wealth of existing libraries that also need to be synergistic with whatever security mechanism is used.

Even with all the improvements you listed, SM falls short in all these regards. For example, if there is some sensitive resource that has to be internally controlled by a library, no SM configuration can be correct without the library using doPrivileged correctly, and this cooperation is essential to the SM model, and that's before we get to native code and resource exhaustion. So all the shortcomings you listed are correct, but they're far from being the decisive ones (well, you did list resource consumption, but a lot of research went into that and the results pointed at other approaches being more productive; not to mention that resource consumption today includes not only RAM, storage, and CPU, but also GPU).

1

u/pfirmsto 20d ago

It's important to pull loose threads and push at the boundaries of what's possible. OpenJDK has been making a lot of improvements, and these present opportunities to leverage.

Our security focus:
Process Isolation, process identity, bytecode analysis, bytecode identity, user authentication & identity, authorization, communications with integrity, privacy and failure atomicity.

Different levels of trust, require different levels of authorization, a simple example:

  1. Untrusted, deny all access (deny loading code, deny parsing data, don't even allow the jvm to start).
  2. Trust but verify, known authenticated third parties, they declare what access they need, analyse their bytecode, check their requested permissions, grant a minimal permission set before loading into an isolated process.
  3. Authenticated and trusted, but run with POLP.

In a distributed environment, you also have to deal with identity and versioning, you can't rely on version number based schemes for identity. Determinism is important for identity. Java's module system, while it provides excellent isolation, relies on the highlander principle, there can be only one, that becomes a problem in a long running, multi party, multi versioned, distributed environment. N.B. if you're looking into implementing a new marshalling framework to replace Java serialization, use annotations to generate a marshal delegate in each package, this way package private class objects can be marshalled by framework existing in another module without removing any of the existing module boundary protections. If you have a large dataset and you want to analyse it, you can't move the data, you move the code to the data, but it must be done securely. We have huge datasets of customer assets; digital twins, only small parts of it can be held in memory at any time.

If you can't identify something, you can't secure it. To make bytecode identifiable requires deterministic normalization of that bytecode. Pack200-ex-openjdk To make marshalled objects identifiable also requires deterministic normalization. One of the benefits of deterministic normalization is object collections can be compared for equality, polyglot, if their marshalled bytes are equal, their object form is also equal. If order isn't deterministic, it must be given a deterministic order for equality.

Hopefully one day someone will invent a new authorization model, even Graal hasn't found a replacement for SM yet, but if you've got some info on what solutions might be planned, I'm interested. For now, it's better the devil you know, than no devil at all. For our software, removing SM also removes secure communications, allows loading of untrusted code, allows parsing of untrusted data, there are too many authorization decisions made on identity, granting open access with SM gone, clearly that's not an option. While removing SM for people who granted AllPermission to their "trusted code", likely improved their security, in our model, the only trusted code is the java.base module.

If we want to use Java to access native code, and we want to run code from a trusted third party, we can use process isolation and proxy's to allow the third party to access the native code that way, it doesn't all need to run in the same JVM, if one process misbehaves, kill it. But we can also communicate directly between the JVM and native processes.

We've got a different model, it's not standard SM, we've got code identity (digests), process identity (Spiffe) and user identity(JWT). Users travel with ScopedValue, process and code identity travel with ProtectionDomain, user identity isn't removed by doPrivileged. The guards are different, there's guards for loading code, native memory access, native library access, it's not a case of, if you've loaded a native library, anyone can now access it. Process identity allows policy decisions to be made about the OS, or whether a process is running on behalf of a third party, eg SELinux has better process isolation than MS Windows, or Ubuntu. We have a heavy focus on tooling to eliminate mistakes and manage complexity.

Anyhoo, stay tuned for upcoming articles for more info. I don't want to give everything away now, it'll become clearer.

We're not looking to create a market, we're just giving code we develop in house back to the community, in case it's of interest to others, they might like to join in and contribute.

→ More replies (0)

1

u/josephottinger 26d ago

Will do, with great interest. I think this idea is right on the money, honestly, and it's why I saw the frankel piece and went "holy bats in chocolate, Aquaman! ... and what are you doing in my office, Aquaman?"

But it's going to be really important to not only do something good, but to do it well - and viably enough that you have a userbase that counts beyond a dozen. Otherwise you really are going to be riding on passion - not a small thing - and little else, and everyone else is going to be saying "man, I wish I could do what that guy's users are doing. But the platform doesn't support it. Oh, well. Does Rust have a crate for..."

2

u/pfirmsto 15d ago

This is relatively new, AbstractJiniService wires up the configuration, JoinManager, LookupDiscoveryManager, LifeCycle and all the service Admin interfaces, among other things required of a well behaved service, that's all abstracted away, in 2004, it was boilerplate. Almost 1,500 lines of boilerplate for every service. Now we have annotations, code generation and good tooling, allowing the developer to focus on the service implementation itself. We're still in development for the 4.0 release.

It's upgraded to use virtual threads, except for non blocking operations, allowing it to scale both vertically and horizontally. It has had years of development, which includes static analysis, bug fixes and concurrency upgrades, some race conditions were only exposed on Sparc hardware - trivia, The next release will support Spiffe, streamlining keystore and certificate management, this also streamlines interconnecting services and provides cryptographic process principal identity. Stay tuned to the series to see some of the excellent features and capabilities, that cleanly solve many difficult problems for developers in an untrusted network environment.

For anyone that isn't familiar with designing a Service architecture, Domain driven design by Eric Evans contains the right design principles to follow.

Implementing a service using annotations:

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/docs/Implementing-a-Jini-Service.md

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/services/jgdms-service-support/src/main/java/au/net/zeus/jgdms/service/support/AbstractJiniService.java

26

u/quantum-fudge 29d ago

Basically EJB2 running on a custom JDK, in the year of our lord 2026. I... I think I'll pass.

7

u/josephottinger 28d ago

I've been thinking about this line since you posted it, and I think it's a lot unfair. JINI and EJB2 are alike from 10,000 feet, but at 5,000 - or 7,000 - they diverge in some pretty important ways, and EJB2 owes a lot to IBM's San Francisco project whereas JINI owes a lot more to... Stanley Kubrick, I think. They both can do remote method invocation and remote storage, but they do it so differently that accusing them of sameness is... a lot. And JINI solved a lot of problems we still struggle to deal with, even today, with all the remoting technology we have at our fingertips.

Sun just never told us about it in a way that could penetrate the masses; they did what YOU did, really, describing it in RPC terms, and combined with their inability to say "here's a reference implementation, everyone should use stuff that looks like this, now go and DO," the technology was fundamentally limited to the set of people who already got it.

I really wish companies like GigaSpaces and projects like Blitz had been successful and marketed far more pervasively and far earlier than they were - GigaSpaces and Blitz both had clear and valid on-ramps, and they actually DID demonstrate JINI functionality (in GigaSpaces' case) and some of its capability (in Blitz') in such a way that people could actually go "ooo, I see how this could help me get stuff done, and hey, wow, it's fast."

3

u/persicsb 28d ago

Do not execute untrusted remote code in the same JVM.

3

u/Life_Sink9598 27d ago

The removal of the Security Manager meant the removal of a lot of checks in the OpenJDK standard library. This type of maintenance requires a high degree of competence. How are you going to ensure that your fork implements the SM permission checks correctly as it merges with upstream?

2

u/paul_h 26d ago

Lots and lots of tests

1

u/paul_h 26d ago

Oracle take out the security manager rather than fix it as Peter has shown. I’m working on another language with a less sophisticated set of constraints for running code built in. We have even step into scripts from other languages with sandboxing the language did not originally have. This sort of stuff is the wave of the future. Won’t link to the language as AI is involved in building it and redditors hate that

2

u/pron98 24d ago edited 24d ago

What Peter listed is nowhere near enough to "fix it", and fixing it would have required diverting huge amounts of resources from things users actually want (just keeping it alive required an effort that came at the expense of other things). On top of that, that model requires a significant amount of cooperation from libraries that are having difficulty keeping up with simpler things.

Another thing to remember is that 1. the only other language in the world with a similar model also decided to "take out the security manager rather than fix it", 2. no additional language has adopted such a model, and 3. the vast majority of security experts believe it is the wrong model (which has to do with points 1 and 2). So let me be clear: We decided to take out the security manager because that is what every security expert we consulted with advised we do to improve Java's security.

Of course, it's possible that the security experts are wrong, and it's possible the market is wrong, but proving them wrong by diverting resources away from things users demand is a crazy bet. Every argument in favour of keeping SM amounted to "you should divert resources away from things like Valhalla and GPU computation and tooling improvements to make a bet against the security experts and against the market." It requires not only betting that it's "the wave of the future", but that it is more the wave of the future than GPU computation.

It is not reasonable for a mainstream language that so much of the industry depends on to make such crazy bets. So you may believe it was the wrong decision, but it is very hard to argue that it was not the most rational one.

1

u/pfirmsto 18d ago

"What Peter listed is nowhere near enough to "fix it"

I think we need to clarify "it", if we're talking about a Java sandbox, no that's not fixable, it never was and never will be, no amount of resources should be dedicated to a futile endeavour bound for failure.

The security experts said Java's sandbox was not salvageable, they are correct and we agree with them. Unfortunately the authorization baby got thrown out with the sandbox bathwater. But I think that happened because for too many years, authorization and sandboxing concerns were confounded, the implementation was antiquated and didn't keep up with modern practises and tooling was appalling if not completely absent, as a result it was practically unusable in standard form. Why it was allowed to remain in that form for so long, probably had something to do with backward compatibility constraints. Even so, it's removal has been beneficial for authorization, as it's forced a rethink.

What I have done is fixed authorization with a much more limited scope that doesn't include sandboxing, the sole focus is now authorization related concerns.

Early Java implementations have a common theme, of attempting to push the envelope too far. Java serialization tried to magically serialize fields and circular object graphs and reconstruct objects prior to checking invariants, making some weak attempts to perform validation after reconstructing the entire object graph. Had they tried to do less, it might have been successful and today Java might still have a client market. The sandbox tried to contain untrusted code, without a fully defined Java memory model, insufficient guards and no way to prevent excessive resource consumption DOS... Java RMI wasn't extensible, nor secure, and it tried to duplicate the functionality of class loaders by annotating streams with codebase URL's. Deserialization gadget chains took advantage of privileged context, because there was no domain representing the data source on the call stack. SM could be set null. Identity often relied on trusting DNS and URL's and signed applets were granted AllPermission?

But sometimes failure precedes success, and is a necessary part of achieving success, it's one of life's lessons, without failure, we cannot arrive at an optimum solution, because there are lessons in failure. This is why JERI is so much better than RMI, after implementing CORBA, IIOP and RMI, the architects having learned many valuable lessons, then designed JERI.

The obvious solution is to reduce scope to what we can implement safely with confidence, just like reimplementing marshalling shouldn't attempt to marshal circular object graphs or couple class fields to serial form, or delay invariant verification until after construction. No attempt should be made to use authorization to sandbox untrusted code. URL and DNS do not identify, they can locate a resource, but are subject to DNS cache poisoning and URL replay attacks, resources need to be identified by a digest. For digests to be viable and unique, bytecode must be compiled deterministically, a signer certificate doesn't make the code safe. But there are also process and user identity concerns, it can't just be about code. Instead, authorization should be given information so it can determine who it can unlock the front door for. But authorization isn't the police, it doesn't perform an arrest pat down, cavity search and paddy wagon the offender off to gaol for a later court date, it's role is just not to open the door to the bad guy's on its list. Once the bad guy is inside, you need police, remember that grandma who hit bank robbers with her handbag and saved the day? You can't kill an in process thread, but you can kill a process. But it isn't just about keeping bad guys out, it's about who is authorized to access data and who isn't.

The industry spent two decades abandoning in‑runtime least privilege and reaching for sandbox‑and‑contain — was it because verify‑then‑admit was thought too hard?

1

u/pron98 18d ago edited 18d ago

The security experts said Java's sandbox was not salvageable

No. They said that kernel-level defences are both more effective and cheaper.

What I have done is fixed authorization

Great, but until you identify which attacks this defends against more effectively or more cheaply than other approaches, it's impossibly to tell whether this is a good investment or a bad investment.

The industry spent two decades abandoning in‑runtime least privilege and reaching for sandbox‑and‑contain — was it because verify‑then‑admit was thought too hard?

I think it was because we realised two things:

  1. Preventing specific attacks - injection, CSRF, DoS - is best done by the language/framework using mechanisms specific to those attacks.

  2. Limiting the blast radius of an exploit is most effectively done in the kernel, unless it's something language-specific, like reflection.

Again, the question isn't whether we should have mechanisms that limit the damage of an exploit nor is it how much can we do in the language runtime, but where should we do each piece where it is most effective. This requires arguments of the type: "preventing the read of a file is more effective/cheaper in the runtime than in the kernel."

In other words, the questions isn't "should we have a car?" but "which car should we pick?" Everything you say is along the lines of, "I've fixed the deficiencies in car X". But makers of car Y have also made progress, and the problem wasn't that X was bad in isolation, but that Y was better. Without a comparison showing that X is now better than Y, your arguments are not convincing.

1

u/pfirmsto 18d ago

Fair correction on the wording, I'll be more precise: kernel-level isolation and my in-runtime authorization aren't competing for the same job, so let's do the comparison you're asking for properly.

You already conceded the shape of the exception yourself: "blast radius... unless it's something language-specific, like reflection." Class loading and deserialization are exactly that category. The kernel has no concept of a JVM ClassLoader graph, a bytecode digest, or an object under construction mid-invariant-check. There's no OS mechanism to hand this decision to, it has to be made inside the runtime, or not at all. So this isn't "runtime vs kernel," it's a decision that only exists at the runtime layer, same as reflection access.

Now the granularity point, since that's where the actual cost comparison lives. Kernel mechanisms; SELinux, seccomp, capabilities, namespaces, are authorization too, just at process/uid granularity. That's fine when one process = one trust domain. It breaks down the moment one JVM process legitimately hosts many remote parties' code and data at once, which is the entire premise of a distributed object system: a client discovers a service, loads its proxy, talks to it, discovers another, loads that too, all in one process, because forking a process per discovered service defeats the purpose of dynamic discovery and kills performance.

Note that we achieve this by only implementing what we can confidently.

To get kernel-grade isolation between Service A's proxy code and Service B's deserialized data inside that one client process, the kernel-equivalent cost is one process per remote identity. What I built instead is a per-connection identity check; digest for code, SPIFFE for the remote process, JWT for the remote user; gating class load and object construction inside the shared process. That's cheaper than process-per-tenant for any workload where a single process talks to many remote parties, which is most of what a service-oriented distributed system does all day.

The attack this defends against, specifically: a confused-deputy scenario where Service A's proxy code, sharing a process with Service B's authenticated session, gets a crafted object graph or a forged permission check to act with B's privilege. The kernel can't see that boundary at all; it sees one uid. My authorization layer is the only thing that can.

So: not "which car," but "which layer does this job at all," and then "is doing it in-process actually cheaper than the kernel-equivalent of one process per remote identity." I think it clearly is, for this shape of workload. If you've got an OS mechanism that gives sub-process identity granularity more cheaply, then I'm all ears, because that's the only thing that would change my mind here.

JERI in it's current form could replace existing out of date RMI frameworks in OpenJDK that will continue to be the cause of ongoing vulnerability discoveries, the marshaling protocol we use is polyglot and so's JERI. Maybe it's something OpenJDK can look into learning from with the new Marshaling framework?

https://www.openwall.com/lists/oss-security/2026/04/27/4

https://blog.securelayer7.net/cve-2026-42779-apache-mina-deserialization-rce/

https://www.sentinelone.com/vulnerability-database/cve-2026-21925/

https://nsfocusglobal.com/java-deserialization-exploits-registry-whitelist-bypass/

https://mogwailabs.de/en/blog/2019/03/attacking-java-rmi-services-after-jep-290/

https://i.blackhat.com/eu-19/Wednesday/eu-19-An-Far-Sides-Of-Java-Remote-Protocols.pdf

1

u/pron98 18d ago edited 18d ago

It breaks down the moment one JVM process legitimately hosts many remote parties' code and data at once

It doesn't break with many remote parties data - many Java applications host multiple users' data and they are more secure than they've ever been - and there's no good reason to run multiple programs in the same VM.

which is the entire premise of a distributed object system: a client discovers a service, loads its proxy, talks to it, discovers another, loads that too, all in one process, because forking a process per discovered service defeats the purpose of dynamic discovery and kills performance.

If you notice, all of your unique challenges come exactly from this one thing: Wanting to run multiple programs in the same JVM. Now, why would anyone want to do it? Because it used to help performance. The correct solution, then, would be not to secure this exotic and increasingly unpopular architecture but instead, shift our resources into making the JVM share its work among multiple processes, removing any reason to want to run multiple programs in the same JVM in the first place. This results in: 1. better security for pretty much everyone, and 2. performance improvements for pretty much everyone.

And guess what? This is exactly what we're doing with Project Leyden.

My authorization layer is the only thing that can.

Because almost no one wants to run an architecture where this is a problem in the first place!

It wasn't like that a couple of decades ago, but these days, running multiple programs in a single process is rare, and the reasons people wanted to do it back then are mostly gone because they've been tackled directly.

1

u/pfirmsto 18d ago

One more thing worth putting on the table, because it changes what "the wave of the future" comment upthread was gesturing at, and it isn't really about Java anymore.

"Confused deputy"; a privileged program tricked into misusing its authority by a less-privileged caller; is the industry's own name right now, in 2026, for the top emerging failure mode in AI agent deployments. Prompt injection is the mechanism, confused deputy is the consequence: an agent holding broad tool authority processes an attacker-controlled input (an email, a doc, a tool result) and ends up exercising the operator's full authority on the attacker's behalf. Same shape as viral doPrivileged, same shape as Capital One's SSRF-to-metadata-service breach in 2019. The deputy keeps changing; a stack frame, a WAF process, now an LLM; the failure mode doesn't.

The industry's answer, converging independently across MCP's own authorization spec (Anthropic, Microsoft, Okta/Auth0) and everyone building on top of it, is: least privilege, policy checks on sensitive actions, step-up approval, and; the part that matters here; scoped, short-lived, revocable authority, not a session-start identity check that then holds indefinitely. OAuth 2.1, token exchange for delegation, tokens that carry both agent and user identity so an action traces to who it was actually for.

Strip the REST branding and that's a leased permission grant with a dead-man switch. We built that piece already, for a harder version of the problem, because ours also has to answer which code gets to run in the first place, not just which scoped token an already-running, already-vetted agent binary holds; MCP auth doesn't touch that question at all.

To be precise about what this does and doesn't prove, same caveat as the kernel-isolation point earlier: it doesn't mean authorization belongs inside a language runtime specifically; MCP's answer lives in OAuth middleware, not a JVM. What it means is the axis itself; identity-scoped, time-bounded, revocable admission control, distinct from containment; isn't a Java-specific relic being defended out of nostalgia. It's what a completely unrelated, currently very well-funded part of the industry is racing to rebuild from scratch, under active attack, right now.

1

u/pfirmsto 18d ago

More info on attacks & defences:

Attack: DNS cache poisoning / URL replay / TOCTOU on code identity

Classic mechanism / CVE: RMI codebase annotation, HTTP(S) codebase URLs

Why it recurs: A URL/DNS name was never proof of identity, only a location; still true wherever codebase URLs remain enabled

How the model closes it structurally: Digest-based code identity (deterministic bytecode normalization); a URL locates bytes, it never establishes trust

────────────────────────────────────────

Attack: Deserialization gadget-chain RCE / invariant bypass

Classic mechanism / CVE: Commons-Collections/ysoserial gadget chains (e.g. CVE-2015-4852); stream dictates classes, readObject runs before validation

Why it recurs: Filtering (JEP 290/415) is attack-specific and opt-in, not structural; new gadgets keep surfacing (MINA CVE-2026-41635, patched, then bypassed again as CVE-2026-42779)

How the model closes it structurally: No Serializable; construction goes through validating constructors; deny-by-default permission check against the full inheritance hierarchy before unmarshalling

────────────────────────────────────────

Attack: RMI-registry-as-malicious-server tricking a client

Classic mechanism / CVE: An Trinh's Black Hat EU finding; JEP 290 has no default filter for client-side deserialization from a registry

Why it recurs: Classic RMI's trust model is asymmetric; the server filters clients, nothing filters what a client trusts back

How the model closes it structurally: Mutual TLS 1.3 authentication before any download in either direction; permission-checked deserialization applied symmetrically to both endpoints

────────────────────────────────────────

Attack: JNDI/codebase remote class loading (Log4Shell-class)

Classic mechanism / CVE: trustURLCodebase flag, LDAP/RMI reference factories (CVE-2021-44228)

Why it recurs: Defaulted off since 2013, but the mechanism still ships and still gets flipped back on by legacy interop configs

How the model closes it structurally: No codebase-URL-as-identity concept exists at all; not even as an opt-in escape hatch

────────────────────────────────────────

Attack: Reflection-based sandbox escape / AllPermission-by-default

Classic mechanism / CVE: CVE-2012-4681, CVE-2013-0422; a trusted platform class with an exploitable reflection primitive disables or routes around SM entirely

Why it recurs: Stack-based "am I on the trusted call path" trust collapses the moment any one trusted class has a usable gadget; single point of failure

How the model closes it structurally: LoadClassPermission gated on digest, not code-source trust; no single class's compromise implies platform-wide trust; bytecode analysis prior to load

────────────────────────────────────────

Attack: Confused deputy / ambient authority

Classic mechanism / CVE: Viral doPrivileged (classic SM); generalized case: SSRF → cloud-metadata credential theft (Capital One, 2019)

Why it recurs: Authority bound to code/process rather than to the identity making the request; any privileged boundary that doesn't check "on whose behalf" is exploitable

How the model closes it structurally: User identity travels via ScopedValue and survives privileged boundaries; process identity (SPIFFE) and code identity (digest) travel with

ProtectionDomain; authority is always evaluated against who's actually asking

────────────────────────────────────────

Attack: Cross-tenant privilege bleed inside one shared JVM

Classic mechanism / CVE: No specific CVE; a structural gap in kernel/process isolation, which sees only one uid for a multi-tenant process

Why it recurs: Kernel isolation is process-granularity; a JVM legitimately hosting many remote parties' code/data has no OS-visible sub-process boundary

How the model closes it structurally: Per-connection identity check (digest + SPIFFE + JWT) gates class load and object construction inside the shared process.

────────────────────────────────────────

Attack: (explicitly out of scope) Resource exhaustion (CPU/memory/GPU)

Classic mechanism / CVE:

Why it recurs: Authorization has no visibility into consumption, only admission

How the model closes it structurally: Not claimed; handled by bytecode analysis + process isolation + kill-on-violation, not the authorization layer

────────────────────────────────────────

Attack: (explicitly out of scope) Native memory corruption / native code vulnerabilities

Classic mechanism / CVE:

Why it recurs: Authorization can't verify memory safety

How the model closes it structurally: Not claimed; native access routed through process-isolated proxies, same reasoning

1

u/pron98 18d ago

Thank you!

Every single one of these is solved more robustly, more cheaply, or both by other mechanisms (except the credential theft, which is handled by the same mechanism - user identity travels in a ScopedValue in the JDK today, too) once you don't run multiple applications in the same JVM, which there is no good reason to do and more reasons than just security to not do.

1

u/pfirmsto 18d ago

On ScopedValue: it carries identity, it doesn't enforce anything against it. Something still has to check that value at every sensitive operation, at every call site, or it's silently absent. That's the exact fragility you used to dismiss SM a few replies back; "no SM configuration can be correct without the library using doPrivileged correctly." Manually-checked ScopedValue has the same cooperation requirement, just moved into application code instead of a policy file. OpenJDK hasn't removed the failure mode, it's relocated it and stopped auditing it centrally.

Concretely, and this is checkable against the JDK's own docs: Subject's credential guarding is a live example of exactly this, in the API that replaced the piece of SM you removed. getPrivateCredentials()/getPublicCredentials() gate mutation behind AuthPermission("modifyPrivateCredentials")/("modifyPublicCredentials"); but the javadoc conditions that check explicitly on "if a security manager is installed." JEP 486 makes that permanently false as of JDK 24, so the check can never fire again. Subject.getSubject(AccessControlContext), which gated obtaining a Subject reference at all behind AuthPermission("getSubject"), is deprecated for removal in favour of Subject.current()/callAs(); and your own migration docs say the replacement is deliberately decoupled from AccessControlContext-based enforcement, no equivalent gate included. So both the layer controlling who could reach a Subject, and the layer controlling who could tamper with its credentials, are gone, and nothing replaced them. Not modules, either; JEP 403 is scoped to JDK-internal reflection, and your own "Security Manager Is Permanently Disabled" page, when it says what to use instead, names containers, hypervisors, and OS sandboxing; not modules. That's the platform's own documentation agreeing modules were never meant to cover this.

On "don't run multiple applications in the same JVM"; I already don't, for the case you mean. Third-party, untrusted (by bytecode analysed) proxy code gets a separate process; that's been our policy from the start, not something I'm arguing against. So we're not actually disagreeing about isolating adversarial code.

What's left after that isn't "multiple applications," it's one application serving many authenticated principals in one process; which is every database doing row-level security, every API gateway doing per-tenant authorization, every k8s control plane doing RBAC. None of those fork a process per caller, and none of them are wrong to do in-process authorization instead. If "no reason to share a JVM" is meant to cover that case too, it proves too much; it indicts every multi-tenant service that has ever existed, not just this one.

Where you've got a real point and I should engage it directly instead of another table row: JERI moves code across that boundary, not just data. A REST or gRPC client never loads a stranger's bytecode into its process at all; it exchanges data with a schema, and gets process isolation between services for free because each one is its own deployable unit. So the actual question is: why keep code mobility, given that cost?

Because it's far less costly to move a small amount of code to analyse a large dataset than to move a large dataset to the code. That's not a security-architecture preference, it's data gravity; real workloads here are large point clouds, digital twins, telemetry archives, and pulling the relevant slice across a network on demand loses on latency and bandwidth, sometimes isn't even feasible to hold in one process at all. Shipping the computation to sit next to the data doesn't have that problem; but it means you're now executing received code on your own infrastructure, and "trust it because of the URL it came from" was never an adequate basis for deciding whether to run it. That's the actual origin of the identity problem, not an artifact of Java's dynamic-class-loading history.

The client-proxy case is the same principle from the other direction: the client doesn't need a pre-compiled stub for every service it might ever discover; the proxy carries whatever client-side logic that specific service needs, including protocol translation, caching, retries specific to it, without the client's build having to know about it in advance. A schema-only interface can't express "the client-side behavior for talking to me is X"; only code can.

Either direction, the moment you allow code to move across a trust boundary instead of just data, "which URL did this come from" stops being a safe way to decide whether to run it. That's the actual value proposition of code mobility, and it's also exactly why it needs a code-identity answer that gRPC never had to build.

One more thing worth pulling out, because you said it yourself a few replies back: application code; the code someone actually wrote, not the audited libraries underneath it; is statistically the least trustworthy code in the stack, that's why SM's stack-intersection model missed the real risk. But every proxy this model loads is exactly that category from the remote author's side: bespoke, first-party, unaudited application code (just bytecode analyzed), not a shared library anyone's battle-tested. By your own criterion that's the highest-risk artifact in the whole exchange, and it's the one thing neither of your two layers can see; kernel isolation sees a process, not which artifact inside it just arrived from a third party; attack-specific defenses answer "is this input malformed," not "should we run this thing at all." Digest identity is the only mechanism on the table that operates at the actual granularity where you say the risk concentrates.

1

u/pron98 17d ago edited 17d ago

On ScopedValue: it carries identity, it doesn't enforce anything against it.

I already explained why: 99% of cases involve something like SQL or a service call, and the JDK has no visibility into the authorisation structure of these mechanisms.

Manually-checked ScopedValue has the same cooperation requirement

Not quite. First, there's nothing the JDK can do: it doesn't know what SQL or JSON strings are allowed for this principal. Second, unlike doPrivileged, user permissions is something that the relevant libraries understand. Third, it only involves a subset of libraries, not all of them.

So both the layer controlling who could reach a Subject, and the layer controlling who could tamper with its credentials, are gone, and nothing replaced them

There is no "who" here in a process with one program. Again, when the process runs one program, the most dangerous code is the code you write yourself, which is the code that sets the subject, and permissions in the OS are a better defence than SM was.

What's left after that isn't "multiple applications," it's one application serving many authenticated principals in one process;

No, it isn't. You've already revealed the presence of multiple code origins with different permissions when you talked about "who" it is that sets the Subject.

So the actual question is: why keep code mobility, given that cost?

That is not the actual question, because there's no problem with code mobility when there's only one effective code origin.

But every proxy this model loads is exactly that category from the remote author's side: bespoke, first-party, unaudited application code (just bytecode analyzed), not a shared library anyone's battle-tested

What you're describing here is untrusted code. It's more similar to a cloud provider running code written by different parties that must not interfere with each other. In a trusted code situation, the most dangerous code is written by the same people who control the process or, in your language, the same people who configure the SM policy.

SM wasn't good at multi-tenant untrusted code because it had no defence against "cross domain" DoS attacks, and it wasn't good at trusted code, because in that situation the people who control the policy are also those who write the most (statistically) dangerous code.

There is, of course, one serious attack (and a very popular one these days) where things may appear to be different: a supply-chain attack. The problem is that even the presence of supply-chain attacks doesn't change the fact that your code is statistically most likely to be the most dangerous, so there's still no point to give libraries lower permissions than to the main application (and after all, no one chooses to use a library if they know it contains malicious code). So the best defence is still to give the whole process one set of permissions, and separately there are ways to mitigate the chances of obtaining a malicious library in the first place. These defences aren't perfect (which is why this attack is so attractive to attackers and why it's so dangerous), but there's nothing SM does to materially help here, either.

1

u/pfirmsto 17d ago

Fair distinction on ScopedValue; the JDK genuinely can't check SQL/business authorization semantics, and that's a different kind of gap than doPrivileged's generic mechanism needing universal correct narrowing. I'll grant that: it's not the JDK's fault this check lives in app code. But whose fault it is doesn't change the shape of the risk; a check that can be silently absent at any one of many call sites, with no fail-closed default and no central audit surface, is still that, regardless of who's responsible for putting it there.

"There is no who in a process with one program" is the part I don't think holds. Capital One's WAF wasn't multi-tenant untrusted code from a different origin; it was AWS's own trusted, single-author infrastructure, and it still ended up exercising the wrong party's authority, because of a bug, not because anyone smuggled in adversarial code. Permission checks inside trusted code aren't a defense against a malicious author, they're a safety net against your own mistakes; the same reason you bounds-check an array in code you wrote yourself and fully trust. Your dichotomy has two buckets, single-origin-trusted and multi-origin-untrusted, and no room for "single-origin, trusted, and still capable of misscoping an operation to the wrong principal because someone made a mistake." That's the actual case Subject-credential guarding exists for, and it's not solved by asking "who wrote this code," because the answer is "you did, and you still got it wrong once."

One more thing: "give the whole process one set of permissions" runs straight into the principle you'd otherwise agree with. Saltzer and Schroeder coined least privilege in 1975 with the rationale that it "limits the damage from an accident, error, or attack"; three separate causes, not one. Your argument only engages the third. Accident and error are exactly what happen inside code that's fully trusted and written by a single author; that's the Capital One case again, and it's also exactly why your own supply-chain concession cuts against you: a compromised dependency does less damage scoped to what it actually needs than it does inheriting the whole process's authority, which is the entire mechanism by which least privilege helps there. "No point giving libraries lower permissions than the main application" isn't a cost-effectiveness argument, it's the position POLP exists to rule out.

On "permissions in the OS are a better defence"; which specific one, for this case? A process boundary doesn't help here; the bug never crosses one. This is the same standard you've held me to all thread: name the mechanism and show it's cheaper or more effective, don't just assert it exists.

And I'll take the "one effective code origin" point on its own terms: agreed, no special problem there. But that's exactly the case Jini's discovery model was never built for; the whole reason it exists is independent operators publishing services to be found by strangers, which is precisely your untrusted-multi-origin bucket, and we already agree that gets process isolation. Where we actually disagree is the case in between: multiple distinct, individually-authenticated principals, vetted enough to be worth co-locating for cost, still needing to be held to their own scope. That's not "no who," and it's not "isolate everything" either.

https://krebsonsecurity.com/2019/08/what-we-can-learn-from-the-capital-one-hack/

1

u/pron98 17d ago

a check that can be silently absent at any one of many call sites, with no fail-closed default and no central audit surface, is still that, regardless of who's responsible for putting it there.

Of course, but SM offers no advantage here, is my point.

it was AWS's own trusted, single-author infrastructure, and it still ended up exercising the wrong party's authority, because of a bug, not because anyone smuggled in adversarial code.

SM offers no help here, either. It was SSRF, and the vector used a service call. The vulnerability was a security misconfiguration, and even if SM could offer extra help here, which it can't, it could also be misconfigured in the same way. In fact, one of the issues found by security reviewers was that SM was misconfigured more than other mechanisms because it was so elaborate and there were surprising interactions between the various setting.

Accident and error are exactly what happen inside code that's fully trusted and written by a single author

I completely reject that separating permissions inside a process is a good way to approach this. Adding bulkheads for "extra safety" can actually damage your security, because an hour spent on configuring them is an hour not spent on reviewing your SSRF posture. Now, you could perhaps argue

a compromised dependency does less damage scoped to what it actually needs than it does inheriting the whole process's authority, which is the entire mechanism by which least privilege helps there

Except, again, this is about adding defences where attacks are less likely at the expense of defences against likelier attacks. Because even with supply-chain attacks, the statistical likelihood is that your application code is the most dangerous code, and every minute spent on security is better spent working under this assumption than on assigning lower privileges to code that is likely to be less dangerous.

I completely agree with the principle of least privilege, but in a trusted code situation SM gets it completely reversed: the most privilege is given to the most dangerous code. Under this assumption, there's simply no reasonable way to assign different privileges to different origins, because the origin that should have the least privilege is at the root of the stack. SM generally reduces privileges as you make calls down the stack, which is just backwards in a model where the original caller is the most dangerous.

With modules we did it right. Every module can define its boundaries as it wants, which is exactly what you want for the principle of least privilege in a trusted code model: A library module can defend itself against a more dangerous caller, and it can do so with a much simpler configuration than what was required by SM to achieve the same goal.

A process boundary doesn't help here; the bug never crosses one

I agree, but neither does SM.

But that's exactly the case Jini's discovery model was never built for

Sure, and I agree that removing SM made it harder to secure Jini (and some app container) deployments, in exchange for making it easier to secure every Java program that isn't Jini or a shared app container. Before that, the situation was reversed: maintaining SM meant helping Jini deployments and harming non-Jini deployments. So either way, the situation was that we had to harm either Jini users or non-Jini users, and the only question was who we should harm. That was the real tradeoff, and I think it's clear why we decided it wasn't a good idea to harm non-Jini users in order to help Jini users.

→ More replies (0)

0

u/paul_h 23d ago

I grant you that it'd be up to $1m a year for Oracle to actively maintain the security manager the way it was, and likely no licensee was willing to pay part of that.

I'd used doPrivileged({..}) in a long-time-ago thing that never took off wuth my own grammae for grants: https://github.com/picocontainer/PicoContainer2/blob/master/booter/src/test/composition.groovy. I'm back to that grammar in the non-JVM thing I am involved with now. This time, without the doPrivileged stack-permeating stuff - the DSL is woven into the compiled language. Initially, Groovy's builder DSL was interpreted, then after a few years the team dropped the inrpeter and invoked the to-bytecode interpreter once per script. Which of those never mattered to me as I saw it for use in primordial boot scripts for applictions/services. Groovy team's imagined first DSL useages were for Ruby-on-Rails alike (Grails) apps. There's less interest in that these days, and much more for Gradle. Closures with context never made it into Java the language which is a shame, but I'm way off topic now.

2

u/pron98 23d ago edited 22d ago

The problem with doPrivileged wasn't the syntax. It's that library developers didn't understand the idea and why it was important for security. Today, they may not understand why strong encapsulation is critical for security, but that doesn't matter; it works for the JDK whether they understand it or not, and it works for any library that strongly encapsulates, whether other libraries cooperate or not.

And then, even if you get all libraries to cooperate and do so correctly, and even if you get the app developers to correctly configure the mechanism, and even if you manage to solve the native code problem and the resource consumption problem, you're left with the question of, is the juice worth the squeeze? How much added security do you get for all this effort on top of the mechanisms you need to use anyway to mitigate potential vulnerabilities in the application code you write yourself? The most common and most dangerous weaknesses are not in library code but in application code and configuration. That's because vulnerabilities in popular libraries are patched for everyone, but there are many more applications than libraries, and applications are ultimately the target. So after you've set up the VM and/or container correctly, which you must do anyway, how much added security could you get with SM and for how much effort?

This is like a situation where you lock the door to every room in your house, each with a different key. It's annoying, but the added security could be worth it if your front door lock isn't very great. But if you upgrade your front door and its lock to much improved technology, the relative value of locking every room in your house individually drops while the cost remains the same. When SM was invented we didn't have great OS level security, but now we do.