Blaze Labs /

Permission denied, except DELETE: broken authorization in La Suite numérique Drive

FeaturedSecurity
Oct 12, 2026
9min read
A severed access badge remains tethered to a shared folder in trash, illustrating deletion authority surviving access revocation, lit in French blue, white, and red.

La Suite numérique is the French government’s collection of open-source tools for everyday collaboration, including document editing, video meetings, and file sharing.

Drive is its file-storage and sharing application, offered to French public-sector users as Fichiers. It lets teams upload files, organize them into folders, and share access with colleagues. Think of the familiar shared-drive workflow: one person owns a workspace, collaborators add content, and permissions determine who can read, edit, or delete it.

That last boundary matters when someone leaves a team. Revoking their access should also revoke their ability to disrupt its shared files. Our investigation found a case where Drive enforced the first part but missed the second.

This audit was carried out with Claude Code and Codex as part of Blaze’s LLM-powered agentic AI pentest harness. We used these tools to investigate Drive’s authorization logic and exercise the collaboration workflow described below.

In June 2026, while reviewing La Suite Drive’s authorization model, we followed a routine collaboration lifecycle: invite an Editor, let them create a folder, add team content beneath it, and then remove their access.

The removal worked for reading. The former collaborator could no longer retrieve the folder. Drive’s own ability calculation returned retrieve=False.

Deletion told a different story: destroy=True.

The application remembered who had created the folder and treated that historical relationship as continuing authority to delete it. Removing the collaborator’s access record changed their current permissions, but the creator field still matched their account. One DELETE request could move the shared subtree into trash, including files created by its owner.

We independently found and documented this issue in June 2026 but did not report it to the maintainers. Upstream later fixed the same root cause in commit ff8b8960, released in v0.22.0. We are not claiming credit for the upstream discovery, report, or remediation, and our records do not establish when others first identified it. This article documents our independent investigation: the vulnerable authorization decision, the recorded runtime evidence, and the distinction between recoverable disruption and eventual permanent loss.

Following the off-boarding path

Our investigation concerned Drive’s implementation; it does not establish that a particular government deployment was exploited.

The scenario did not require an account takeover, guessed identifiers, or an unauthenticated attacker. Bob had been a legitimate collaborator. Alice granted him Editor access to a shared root folder. Bob created a child folder, Deliverables, and Alice subsequently added a file beneath it.

When Alice revoked Bob’s access, he retained his account and knew the identifier of the folder he had created. The expected boundary was straightforward: an authenticated account without current access should no longer be able to alter that shared content.

The vulnerable snapshot we reviewed was 350a4828f8f31c030d89073091bd27f2b971fe23. Our original report tracked the finding as N6. The recorded reproduction used a restricted sharing configuration and removed the collaborator’s access grant, so the result was not explained by a remaining public link or another intended Editor permission.

Where historical authorship became authority

The decisive code was in Item.get_abilities(), in src/backend/core/models.py. The following excerpt preserves the relevant decision:

Python · relevant excerpt
can_hard_delete = (
    is_owner
    if self.is_root
    else (is_owner_or_admin or (user.is_authenticated and self.creator == user))
)
can_destroy = can_hard_delete and not is_deleted

Root items required the Owner role. For non-root items, however, the expression offered two independent ways to authorize deletion: be an Owner or Admin, or be the authenticated creator.

The first branch represented current authority. The second represented provenance.

Drive’s get_role() calculated access from the item and its ancestors, including applicable team grants. Revoking the grant could therefore remove Bob’s role. It did not remove the creator relationship, and the non-root deletion expression did not require a current role before accepting that relationship.

After revocation, the decision reduced to:

Explanatory state / recorded observations
is_owner_or_admin = False
user.is_authenticated = True
self.creator == user = True
can_hard_delete = False or (True and True)
can_destroy = True  # item is not already deleted

The name can_hard_delete deserves care. It was also used to derive the ordinary destroy ability. A true value did not, by itself, establish that the caller could immediately purge the item through the API; additional permission checks applied to already-deleted objects.

The central defect was the unconditional creator exception. Authorship survived revocation, so a permission derived from authorship survived it too.

Why denied reading did not prevent deletion

A natural question is whether losing read access should have prevented Bob from reaching the folder at all.

In this implementation, listing visible items and authorizing an operation on a known item were separate paths. The detail queryset allowed the API to look up an existing item by identifier. ItemPermission.has_object_permission() then checked the ability associated with the requested action.

Its relevant decision was:

Python · relevant excerpt
abilities = obj.get_abilities(request.user)
# Action mapping and deleted-item checks omitted.
has_permission = abilities.get(action, False)

For the ordinary DELETE operation, the action was destroy. The API did not first require retrieve=True. This separation is not inherently wrong: applications can legitimately define different permissions for different operations. Here, it made the stale deletion grant reachable even though retrieval was denied.

Once the object check accepted destroy, the viewset’s perform_destroy() invoked instance.soft_delete(). The API was not missing an authorization layer. It was enforcing an incorrect authorization decision.

Making the contradiction executable

Our reproduction used Django and Django REST Framework’s test client against the actual model and DELETE endpoint. It created the collaboration lifecycle, removed the access record, and checked the resulting abilities before issuing the destructive request.

The decisive assertions are shown below; fixture setup is omitted:

Python · relevant excerpt
access.delete()
abilities = folder.get_abilities(collaborator)
assert abilities["retrieve"] is False
assert abilities["destroy"] is True
client = APIClient()
client.force_authenticate(user=collaborator)
response = client.delete(f"/api/v1.0/items/{folder.id}/")
assert response.status_code == 204
folder.refresh_from_db()
owners_file.refresh_from_db()
assert folder.deleted_at is not None
assert owners_file.ancestors_deleted_at is not None

Checking the abilities first strengthened the result. An accepted DELETE alone would not establish that access had been revoked correctly. The contradictory ability values showed that read permission was absent while deletion remained available.

The original technical report recorded one passing test and the following observations. This is a condensed reproduction record, not a fresh execution performed for this article:

Explanatory state / recorded observations
core/tests/test_creatordelete_poc.py — 1 passed
revoked collaborator:
  retrieve=False
  destroy=True
DELETE /api/v1.0/items/<folder>/ -> 204
folder.deleted_at: set
owner_secret.txt.ancestors_deleted_at: set

force_authenticate() supplies the test principal directly. This exercise validates the authorization and persistence path; it is not a test of the deployment’s login flow, browser session handling, or identity provider.

The owner-created descendant supplied another important control. The effect was not confined to an empty folder or a file authored by the attacker. The accepted operation changed the deletion state of someone else’s content.

How one folder deletion affected other users’ files

Item.soft_delete() explains the collateral effect. It assigns deletion timestamps to the selected item, then marks eligible descendants if that item is a folder:

Python · relevant excerpt
self.ancestors_deleted_at = self.deleted_at = timezone.now()
self.save(update_fields=["deleted_at", "ancestors_deleted_at"])
if self.type == ItemTypeChoices.FOLDER:
    self.descendants().filter(
        ancestors_deleted_at__isnull=True
    ).update(
        ancestors_deleted_at=self.ancestors_deleted_at,
    )

The descendant update does not filter by creator. It follows the folder tree and applies the ancestor-deletion timestamp to descendants that do not already carry one.

Alice’s file did not need its own deleted_at timestamp to be affected. Its ancestors_deleted_at value recorded that it sat beneath a deleted folder. That is why the PoC checked different fields on the folder and its child.

This is expected subtree behavior after an authorized folder deletion. The vulnerability was allowing the revoked collaborator to initiate it. A legitimate cascade amplified the consequence of an incorrect decision at the parent.

Recoverable disruption, with a conditional terminal consequence

The original reproduction established unauthorized soft deletion. It did not execute the purge job or demonstrate immediate removal of stored file bytes.

Our initial report used stronger permanent-loss language. A June 12 review corrected that framing: the demonstrated effect was recoverable subtree disruption, including collateral trashing of another user’s content. Permanent loss depended on the owner failing to restore the content before recovery eligibility expired and the normal retention pipeline completing.

The source analysis identified the periodic process_item_purge task and default trash and purge-grace settings of 30 and 7 days. Those values describe retention machinery, not proof that the original test waited through it. The precise restore cutoff and physical purge schedule must also be distinguished; the safe operational instruction is to restore within the configured recovery window.

The report additionally recorded that direct hard deletion by the former collaborator was blocked: the permission layer returned 404 to non-owners on already-deleted items. The demonstrated route was therefore soft deletion followed by a possible later purge, rather than immediate attacker-controlled permanent erasure.

For a team, recoverability still leaves an incident to detect and resolve. Removing a contractor’s access should terminate their ability to disrupt shared work. Restoration limits the damage; it does not make the original authorization decision correct.

The upstream correction

The upstream correction was not the result of a report from Blaze. Credit for that work belongs to the upstream contributors and maintainers. Commit ff8b8960bb89239f17c1f300876f451c981e5c55 changed the creator exception to require the Editor role as well:

Python · relevant excerpt
user.is_authenticated
and self.creator_id == user.pk
and role == RoleChoices.EDITOR

For the original restricted-sharing reproduction, the revoked collaborator no longer had an applicable role. The creator match alone could no longer satisfy the condition.

Upstream retained the intended ability of an active Editor to delete an item they created. This differs from our preliminary recommendation to remove creator-based destructive authority or constrain it more narrowly.

The patch included complementary regression tests. One creates a child through the API, revokes its creator’s access, and expects DELETE to return 403 without marking the item deleted. Another keeps Editor access and expects the legitimate deletion to succeed. Model tests check the corresponding abilities directly.

PR #814 merged on August 31, 2026. The September 9 v0.22.0 release lists the revoked-creator deletion correction. Our source-history review places the vulnerable expression in v0.21.2 and the correction in v0.22.0; this does not establish the deployment date of any hosted instance.

During follow-up research, we also identified a variant that bypasses the original correction. We will cover it in a separate write-up.

Testing permission transitions

A static role matrix would not fully describe this bug. The relevant state was not simply “Editor” or “Reader.” It was “formerly an Editor, still the creator, no longer granted access.”

Testing collaboration systems therefore requires following authority through transitions. Create an object under inherited access, let other users build beneath it, revoke the original grant, and then exercise each operation with the former collaborator’s account. Check effective abilities and persisted state, not only what disappears from the interface.

Deletion tests should include content authored by other users. Otherwise, they can hide the difference between removing a personal object and disrupting shared infrastructure.

Finally, retention, restoration, and purge deserve separate assertions. An item disappearing from normal views does not prove its bytes were erased, and a successful soft deletion does not prove a caller can force an immediate purge.

The lesson from the original finding is precise: a historical creator relationship was enough to authorize a destructive operation after current access had disappeared. Provenance is useful for auditability. If it also grants a capability, that capability needs a revocation rule.

References

Affected snapshot:

https://github.com/suitenumerique/drive/tree/350a4828f8f31c030d89073091bd27f2b971fe23

Upstream correction:

https://github.com/suitenumerique/drive/commit/ff8b8960bb89239f17c1f300876f451c981e5c55

Pull request:

https://github.com/suitenumerique/drive/pull/814

Release:

https://github.com/suitenumerique/drive/releases/tag/v0.22.0

Project:

https://github.com/suitenumerique/drive

Do you have questions? Let's talk.

Get in touch with our cybersecurity experts

Read More