Documentation
The Dangerous Convenience of Implicit Ownership
Authorization systems often become complicated one convenience at a time.
Everlock ran into a good example of this while working on repository permissions.
The original rule sounded harmless:
If a user has the same name as a repository, treat that user as the repository owner.
It removed configuration. A freshly created repository could immediately have an obvious owner without requiring another explicit permission record.
For a small system, that feels elegant.
Then different authentication mechanisms entered the picture.
Suddenly a name was no longer an identity.
And a convenience rule had quietly become a privilege-escalation path.
A username is not an identity
Imagine an Everlock repository called:
gudrun
Now imagine an authenticated user also called:
gudrun
If the authorization system contains a rule like:
username == repository name -> owner
then the answer seems obvious.
The user owns the repository.
But where did that username come from?
Perhaps it came from an SSH key.
Perhaps it came from another authentication backend.
Perhaps it was supplied by a remote system.
Perhaps two independent identity sources happen to use the same local name.
The authorization code sees:
gudrun == gudrun
What it doesn't see is whether those two strings actually refer to the same security principal.
That is the problem.
Authentication and authorization are separate questions
Authentication answers:
Who has successfully proved some identity?
Authorization answers:
What is that identity allowed to do?
Those questions are often presented as a clean sequence:
credentials
|
v
authentication
|
v
identity
|
v
authorization
|
v
operation
The interesting part is the identity in the middle.
If authentication produces nothing more than a convenient display name, authorization may accidentally assign much more meaning to that name than the authentication mechanism ever guaranteed.
Suppose two backends produce these identities:
SSH key -> "gudrun"
local account -> "gudrun"
The strings are identical.
The trust relationships may not be.
If authorization only compares the string, it has merged the two security domains.
Implicit rules hide authority
The original ownership rule wasn't stored anywhere.
There was no grant saying:
gudrun may administer repository gudrun
Instead, ownership was derived:
if principal.name == repository.name {
allow_owner_access()
}
That makes the permission pleasantly invisible when everything works.
It also makes it invisible when you're trying to answer a much more important question:
Why does this user have access?
With explicit authorization, the answer can be inspected.
For example:
principal: ssh-key:SHA256:...
repository: gudrun
permission: owner
With implicit authorization, the explanation becomes:
because two names happened to match
That is a much weaker security primitive.
Convenience rules accumulate
Name-based ownership is only one example.
Authorization systems are full of tempting shortcuts:
repository creator -> owner
same username -> owner
local connection -> trusted
member of group X -> write
authenticated -> read
None of these are automatically wrong.
The problem begins when they are scattered throughout application logic.
Then effective access becomes the sum of explicit grants plus every special case:
effective permission =
explicit grants
+ ownership convention
+ authentication shortcut
+ local-user rule
+ legacy compatibility
+ ...
At that point, reading the permission database no longer tells you who can do what.
The actual policy exists in code.
That makes authorization harder to inspect, test, explain, and eventually change.
The bug was in the model, not the comparison
It would be easy to fix the immediate problem by making the comparison more specific.
Perhaps include the authentication backend:
(local, gudrun) != (ssh, gudrun)
That is certainly better than comparing bare strings.
But it still leaves the larger question:
Why should matching a repository name grant ownership at all?
The name collision exposed the problem.
It didn't create it.
The actual problem was that authority could appear without an explicit grant.
Once that became clear, the cleaner fix was to remove the implicit ownership rule rather than make it increasingly clever.
Make ownership a grant
The replacement model is less magical:
identity
|
v
explicit grant
|
v
repository permission
If someone should own a repository, store that fact.
If someone should be able to read it, store that fact.
If someone should be able to push to it, store that fact.
If someone should only be allowed to submit a pull request, represent that separately too.
The resulting configuration contains a little more information.
That is a feature.
Security-relevant state should be visible.
Explicit doesn't have to mean repetitive
One objection to explicit grants is configuration overhead.
If creating a repository always requires immediately creating an owner grant, why not infer the grant?
Because automation and inference are not the same thing.
Repository creation can perform two explicit operations:
create repository
create owner grant
From the user's perspective, this can still be one action.
Internally, however, the resulting state is unambiguous.
This is an important distinction.
Convenience belongs at the interface:
"create my repository"
Explicitness belongs in the stored model:
repository created
owner grant created
You can automate explicit state without making the state implicit.
This makes migrations easier too
Explicit grants have another useful property: they survive changes in authentication architecture.
Suppose Everlock gains another authentication mechanism.
With name-based authorization, every new identity source raises questions:
Can its names collide?
Are its names globally unique?
Can users choose them?
Can they change?
Are they case-sensitive?
Are they normalized?
Those questions still matter for identity management, but they no longer silently determine repository ownership.
Authorization instead operates on a stable principal and its grants.
The authentication system can evolve without requiring every authorization shortcut to be reconsidered.
That reduces coupling between two parts of the system that are already difficult enough on their own.
It also makes testing much more useful
Authorization code benefits enormously from boring tests.
Given:
principal A
principal B
repository X
grant A -> read X
the expected behavior is straightforward:
A can read X
B cannot read X
A cannot write X
B cannot write X
Now compare that with implicit policy.
To test effective permissions, the test suite also has to remember all the conventions that might create authority:
Does A's name match X?
Did A create X?
Which authentication backend produced A?
Is A local?
Does a compatibility rule apply?
The number of combinations grows quickly.
Explicit grants don't eliminate authorization bugs, but they make the state space much easier to enumerate.
For a security boundary, boring is an excellent property.
Names are for humans
There is a broader lesson here that extends beyond Everlock.
Names are incredibly useful.
We want:
gudrun
photos
family-calendar
office
instead of opaque identifiers everywhere.
But human-friendly names are usually poor security boundaries.
They can collide.
They can be renamed.
They can come from different namespaces.
They can be normalized differently.
And, depending on the system, users may be able to choose them themselves.
A security principal therefore needs a stronger identity than the label shown in the UI.
Conceptually:
principal id: 7f0c...
display name: gudrun
Authorization should attach to the first.
Humans can continue using the second.
The permission model became smaller by becoming more explicit
Removing implicit ownership initially sounds like adding bureaucracy.
In practice, it simplified Everlock's authorization model.
There is no longer one path that grants access because a permission exists and another path that grants access because a naming convention happens to match.
There is one question:
Is there a grant that permits this operation?
That also makes narrower capabilities easier to introduce.
A permission such as pull-request submission doesn't have to coexist with an invisible notion that some users become owners through unrelated naming rules.
Everything passes through the same model.
principal
|
+--> read grant
|
+--> write grant
|
+--> owner grant
|
+--> contribution grant
The model is explicit enough to inspect and small enough to reason about.
Security state should leave evidence
The original ownership shortcut saved one piece of configuration.
It also meant that one of the most powerful permissions in the system could exist without being represented anywhere.
That trade wasn't worth it.
A useful rule emerged from fixing it:
If a decision grants authority, the state responsible for that authority should be inspectable.
That doesn't mean every product needs a giant ACL system.
It doesn't mean convenience has to disappear.
It means convenience should create explicit security state rather than replace it.
A user can still click one button to create a repository.
Everlock can still automatically make that user its owner.
But afterwards there should be evidence of what happened:
repository exists
owner grant exists
Not a hidden rule waiting for two strings to accidentally become equal.
Sometimes the safest authorization feature is simply deleting the clever part.