---
title: "Authorization is more than a group ID"
description: "A privacy issue in a service my family uses reminded me that authorization and retention matter - but how you handle and communicate an incident matters just as much."
canonical: "https://gummibeer.dev/blog/2026/authorization-is-more-than-a-group-id"
---

# Authorization is more than a group ID

A privacy issue in a service my family uses reminded me that authorization and retention matter - but how you handle and communicate an incident matters just as much.

I recently stumbled over a privacy issue in a service my family uses.

The service has a pretty normal social-feed-like concept. Users belong to groups, posts can be shared with a group, and those posts can contain photos and personal information.

I won't name the service or company here. The issue is still being handled and, more importantly, I don't want to publish anything that could help reproduce it. This post isn't about blaming a specific vendor anyway.

It's about how one seemingly small authorization mistake can expose a much larger problem in how an application treats sensitive data - and how the response afterwards can either restore trust or destroy even more of it.

## What I saw

After getting access to a group, I could see posts from long before I had any relationship with that group.

Not just a few stale entries around the date I joined. Historical content going back months and years.

Some of that data belongs to children. It includes things like names, faces and birthdays. There was also data of people who aren't part of the service anymore.

The exact amount doesn't really matter to me.

If I shouldn't be able to see one old post, I also shouldn't be able to see 500 of them.

And if the purpose of a photo is to inform a closed group about something happening today, I have a hard time finding a reason why that photo still needs to sit on the server years later.

## One wrong date

I don't know the actual implementation and I don't want to pretend I do.

But the observed behaviour can be explained by an incredibly small mistake. Something roughly like this:

```php
function canView(User $auth, Post $post): bool
{
    return ($post->sharedWithGroup?->is($auth->group) ?? true)
        && $post->publishedAt->isAfter($auth->createdAt);
}
```

Looks reasonable at first glance.

The post belongs to my current group and it was published after my user was created. Fine, right?

Except that `createdAt` isn't the date I joined that group.

Those two dates may happen to be identical for the first group a user joins. That makes the bug even nicer because it works perfectly during the obvious happy-path tests.

Then a user changes groups a year later and suddenly the authorization rule effectively says: show everything in the new group since this account has existed.

Again: I don't know if that's the real bug. It could be configuration, migration data, another authorization rule or something completely different.

But that's almost irrelevant to the point.

The important part is that authorization usually belongs to a relationship, not just to two current IDs.

A user doesn't simply "belong to group 42". They joined it at some point. They can leave it. They can join another one. They might even come back later.

That relationship has history.

So if historical access matters, the model probably needs history as well:

```php
final class Membership
{
    public User $user;
    public Group $group;
    public CarbonImmutable $joinedAt;
    public ?CarbonImmutable $leftAt;
}
```

Now the authorization question can use the actual relationship instead of borrowing a timestamp from something that only accidentally correlated with it once.

## The authorization bug isn't the scary part

Bugs happen.

I've written enough software to know that I could absolutely use the wrong timestamp in an authorization check myself.

What scares me more is when this single mistake is enough to reveal years of sensitive data.

That's where several other decisions have already failed.

Why is all that data still there?

Why can a new membership potentially inherit an unlimited history?

Why isn't there another boundary between "current group member" and "everything this group has ever seen"?

Why does leaving the service apparently not trigger a review of the data that was collected during that relationship?

A single broken condition shouldn't automatically turn into access to an entire historical archive.

That's defense in depth applied to privacy.

## Communication is part of the incident

The active problem I saw was shut down quickly and aggressively after it was reported.

Good.

If sensitive data is accessible to people who shouldn't see it, I would much rather see someone disable too much first and work out the nice UX later.

But incident response doesn't stop at the technical fix.

The communication I saw afterwards described the situation as "technical problems with photos".

That's not the same problem.

Photos weren't merely failing to load. People could access personal data they shouldn't have been able to access.

I don't expect a complete post-mortem while engineers are still investigating. Nobody should invent a root cause just to have something to publish.

But you can still say what you know:

> We discovered that some users could access content they shouldn't have. We disabled the affected content while we investigate and will follow up.

That's enough for a first message.

Say what you know. Say what you don't know. Say what you did.

But don't replace an uncomfortable fact with a nicer one.

If the explanation you publish isn't the problem you actually found, that communication becomes part of the incident.

A bug can make me question a piece of software. How a company handles that bug determines whether I still trust the people behind it.

Especially with sensitive data, trust isn't only about encryption, permissions and infrastructure. It's also about whether I believe you will tell me the truth when those things fail.

## Data minimization isn't just a legal checkbox

I know "data minimization" mostly appears in conversations about privacy policies and GDPR checklists.

For me it's much simpler than that: data I don't have can't leak.

If a photo is useful for a few days or weeks, keeping it forever creates risk without necessarily creating value.

The same applies to metadata around it. Names, birthdays, relationships, group memberships and old posts all become more dangerous when they accumulate forever.

There may absolutely be reasons to keep some data longer. Accounting, contracts, audit logs and other records have their own retention requirements.

But "we already stored it" isn't a retention policy.

And hiding data from the normal UI isn't deletion either.

A retention period should be part of the data model and product behaviour, not a cleanup task somebody hopefully remembers in three years.

For short-lived social content I would much rather start with aggressive expiration and explicitly opt specific data into longer retention than default everything to eternity.

## Personal data doesn't expire at the same speed as the product

A name, a face and a birthday aren't enough to steal an identity on their own.

But they are durable identity material. They are useful for profiling, impersonation, social engineering and connecting information from different sources.

And when the data belongs to children, the timescale gets weird.

An adult affected by a leak today may notice abuse next week, change accounts, replace cards or react to it somehow.

A child can have information exposed today and only discover the consequences 15 or 20 years later.

A birthday can't be rotated like a password.

That's something I think developers need to keep in mind when deciding whether some field is "sensitive enough" to deserve extra protection. The impact doesn't have to happen while the feature is still in production.

## Don't trust the perfect admin

There's another part of this that annoys me as a developer: giving admins unlimited flexibility is often sold as a feature.

Configure the groups however you want. Pick your own retention. Decide who sees what. Keep everything forever if that's easier.

Nice for the feature list.

Not necessarily nice for the people whose data ends up in the system.

Every additional configuration option is another state the application has to make safe. And if one of those states can expose sensitive data, "the admin configured it wrong" isn't a satisfying answer.

Especially not when the people configuring the product aren't security engineers.

I always assume that users will misunderstand settings, click through warnings, forget cleanup tasks and combine options in ways I never expected. Not because users are stupid, but because they have a completely different job and my software is only a tiny part of it.

So dangerous states should be hard to create.

Sensitive defaults should be restrictive.

Retention should have sensible limits.

Leaving a group or service should trigger automated cleanup or at least an explicit review.

Permissions should be scoped to the relationship that granted them.

And agreements made when data is collected shouldn't silently become permission to keep and share that data forever.

## The boring layers matter

The code that eventually caused the behaviour I saw may turn out to be one wrong date.

That's a tiny bug.

But a tiny bug exposing years of data means the application relied on that one condition doing far too much work.

Authorization, retention, data minimization, restrictive defaults, lifecycle cleanup and honest incident communication are boring compared to shipping another feature.

They also make the difference between "this user saw one thing they shouldn't" and "this user suddenly has access to an archive that should never have been available in the first place." And once that happens, honest communication makes the difference between a bug I can understand and a loss of trust that is much harder to fix.

That's the part I'm taking away from this one.
