I used to suspect I didn't like capabilities, but I wasn't sure yet. I'd seen several things that billed themselves as "capability-based security," and all of them felt _wrong_ for various reasons. I didn't look any deeper until someone happened to share [an old article by Chip Morningstar with me](https://web.archive.org/web/20190423070131/http://habitatchronicles.com/2017/05/what-are-capabilities/), and then it was like the heavens opening. It doesn't get more fair than a person who sees capabilities favorably, explaining what they are in the most favorable way they can.

Now I don't just have confidence that I dislike capabilities - I know _why!_

Normally I say "you should read the article and come back," but I want to intentionally formulate things the other way around today. The original article is long. I would like to write a critique that's also a useful guide to the thing being critiqued, so you can read this post first, and see things in the original that might have gone under your radar otherwise.

I'm going to go in non-linear order, and start by explaining what capabilities are:

1. A capability contains a list of actions you're allowed to do, and resources you're allowed to do those actions to.
2. A capability does _not_ contain information on who's allowed to do these things, by design. Capabilities act like bearer bonds - if you have it, you can use it.
3. Since possession is so important, most of their practical use is in sharing them from one entity (human user, process, or OOP object, etc.) to another. You can share a capability without losing it yourself.
4. Capabilities can be narrowed, but not widened - you can make a new capability C2 out of an original C1, with some actions or resources removed - but none *added* that weren't in C1.
5. New capabilities can be made by wrapping the functionality of existing capabilities. For example, copying a file can be implemented if you have the capability to read from the old location and the capability to write to the new location.
6. Capabilities join together the ability to _refer to_ something (like a file path - this is called "designation"), the _implementation_ of actions (optionally), and the _authority_ to perform them on the designated object.

This last point helps you decode the Norm Hardy quote that shows up throughout Chip's article: "don't separate designation from authority." For example, you shouldn't be able to call `open(somefilename)` - that `somefilename` is a designation, and it doesn't have an authority chaperone JB-welded to it! What you need is more like a capability to a directory, which allows you to call `dir.list_children()` to get a list of child capabilities, one of which is what you're looking for, and then you call `child.open()` on whichever one you like. This isn't the only way you could architect a capability model, but it gets the idea across that you inherit objects, and walk them like graphs in various ways they offer.
## The evil of Objects

If you know me a little from the internet already, you know that I am a fierce critic of OOP - "_do_ separate designation from authority" sounds suspiciously like the kind of thing I would scream at someone while shaking them by the shoulders.

So it's no surprise that this central tenet of capabilities would rub me the wrong way, but resolving the tension requires examining _why_ I see OOP as being problematic.

1. Black boxes are harder to reason about. They have internal hidden state that's difficult to account for, that makes room for their behavior to surprise you. There is a reason that pure data is easy to test, and OOP testing is a nightmare of mocks and monkeypatches.[^1]
2. Black boxes cannot be serialized, deserialized, or anything else without the author's consent. This becomes a viral problem when you use a black box as a field within another object, contaminating it.[^2]
3. Because of the interface fitting the boundaries of the original author's imagination, and most of the extension opportunities for classes being lackluster in practice[^3], code made in multiple layers of black boxes tends to be characterized by layers of inefficient glue being the best you can do.

In light of this, it's no wonder that proprietary libraries sold under extractive licenses _love_ a programming model where data is Smart in the same way that an always-listening IoT device is Smart. It's data that can betray you, work against your interests, as a feature. **Object-Oriented Programming is DRM.**

The more you internalize this, the more terrifying it feels that the open source community has mostly _not_ recognized the "not on thy terms but mine" proprietism nature of OOP DRM as something _not_ to invite into our homes. And of course, one of the worst reasons to invite a malevolent entity to have authority over you is the promise of _security._ Capabilities are the worst part of the object model offered as an access control methodology.

Think about it. How do you prevent a process from taking a capability and splitting it in half with an axe, separating the designation from the authority? How do you prevent capabilities from being "forged," which is a process just doing this:

![[ron-swanson-permit.png]]

> Ron Swanson saying "Not to worry. I have a permit." He offers a sheet of paper with the words "I can do what I want. Ron" printed on it in a sea of white space.

That's right. You need DRM to enforce that stuff. You need somebody else's tamper-protected code, intimately deep inside your code. Capabilities are not possible _without_ this feature, and that's why they are - as the saying goes - "defective by design."

What's noteworthy is, for all its own flaws, the ACL model does happen to get this specific thing right. If you're allowed to separate designation and authority, which is spiritually the same as separating data and code, you can eliminate the black boxes from your code, and even have fully serializable process state if you want it. The outside world is cleanly uncertain, the inside world is cleanly certain, and the boundary between them is where it belongs - _the process boundary._ I'm not, I think, some crank who is against security in some general sense, or fallible interactions with the process-external world. It's just that in-process black boxes are so toxic, that they are a bad answer to the _hows_ of security and fallible world interactions.

Every (advertised-as) capability security system I have ever seen reveres "don't separate designation from authority" as gospel for what capabilities mean. The things your code must contain (designations) must be impermeably bonded to things your code must not be able to control (authority), resulting in programs that cannot control themselves.
## Saying Grace

Not everything about capabilities is bad. I actually quite like the delegation model, especially with narrowing. There are shades of that which I don't mind in more conventional operating systems, like inheriting file handles and such on Linux. There are also examples of narrowing (or as Chip calls it, "attenuation") like `ulimit`, or the various BPF tools that can restrict the function calls available to a tree of child processes.

Likewise, the revoker/modulation pattern is a good one. It's quite nice to have some way to run a program in a controlled harness where its IO is whatever the harness says it is. It's good to be able to audit-log the way a third-party uses the credentials you share with it, or cut them off in the event of a security breach.

I would even agree with a lot of the critique of ACLs - I often feel like I can't express what I want with POSIX file access control (especially in the container era), and that I'm not entirely confident that I've set things up correctly. If it chips away at my credibility to say this, that's fine. I've been using Linux for about two decades, much of it professionally, and if *I* get headaches and anxiety from these tools, they're probably a lot worse for people who haven't used Linux for 20 years and 3 jobs[^4]. This is something I think is possible to fix without violating the purity of process data, by doing something capability-like at the correct boundaries, in a model closer to Plan 9. But you do have to give up on POSIX compatibility, the way P9 does, which I'm fine with - that's like shedding a bad ex.

It's also a fair point that identity is far more complex than many computing systems comfortably understand, and that things like scoped access tokens - a capability-adjacent idea - are pretty great for dealing with that!

There's good stuff to be found here. A vision of capabilities that works more like encryption, as in that last example, could actually be very promising, or which relies on process environment like Plan 9 files. Variants of the idea which expect to be able to reach across into your pants and twiddle how they like, on the other hand, represent most serious attempts at capability-based security.

This, unfortunately, includes CHERI's fat pointers, which break multiple of the expectations of serious engineers when designing system software, like pointer width and how you tag them. Prone depends on tagged pointers, and I expect it would be a nightmare to support platforms where pointers cannot be cast back and forth to machine-word-sized uints, or copied as part of larger structures. I don't plan to support them, and I can say pretty confidently that if someone else did all the work for me, I would still reject the PR.

This is not really about "old" patterns or "cowboy" programmers - this is a subtle, fundamental problem of how to nest hazardous, behavior-laden data inside larger program state. It's subtle enough that it's hard to make a persuasive argument why people should *care*, unless they've been untangling other programmers' awful sealed abstractions for a large percentage of their life.

For my part, I'll keep ordering my ice cream without the broken glass in it. It's just less to worry about.

[^1]: I am also a critic of Functional Programming. I think that mocks are a tell of weak underlying principles in the same way that the existence of monads is.

[^2]: If cleanliness is next to godliness, then deserialization is next to forgery.

[^3]: I don't want to fully unpack this today, as it's something I've done before, and other people have done before. There's a reason inheritance fell out of fashion even within the OOP community, and composition - while better - often does not have the linguistic power to overcome bad APIs.

[^4]: This is also not exclusive to Linux, as I keep hinting with the word "POSIX" - I recently had to deal with a very painful variant of this problem on my home OpenBSD server, though [when I was asking for advice about it publicly](https://raphus.social/@MaddieM4/117230496234708068), I didn't focus on permissions, as that was only a slice of the problems that better directory sharing would have solved.