Skip to content

Systemic authorization bypass: all resources accessible cross-organization #368

Description

@lighthousekeeper1212

Security Report: Missing Authorization Layer

Summary

The application has no authorization layer beyond authentication (login check). Once authenticated via OAuth, any user can access, modify, or delete any resource belonging to any other user or organization. There are no Policies, Gates, or organization/team membership checks on any controller action.

Findings

1. Note Deletion Without Ownership (NoteController line 31-38)
The ownership check is commented out:

$note = Note::find($id);
//->where('user_id', Auth::user()->id)->firstOrFail();
$note->delete();

While CommentController::destroy() correctly uses ->userActive()->firstOrFail().

2. Comment Edit Without Ownership (CommentController line 16-25)
edit() uses bare Comment::find($id) without ownership check. The same controller's destroy() and update() correctly use ->userActive()->firstOrFail(). Classic 1-of-3 inconsistency.

3. Sprint CRUD Without Organization Check (SprintController)
edit(), update(), destroy(), statusUpdate() all use Sprint::slug($slug)->first() without any organization or team membership verification. Any authenticated user can modify any sprint.

4. Issue CRUD Without Organization Check (IssueController)
Same pattern as sprints. show(), edit(), update(), statusUpdate(), destroy() all use global Issue::slug($slug) lookups. The IssueMiddleware that is applied to issue routes has its entire logic commented out.

5. UserStory CRUD Without Organization Check (UserStoryController)
Same pattern. All CRUD operations use global slug lookups.

6. ProductBacklog Without Organization Check (ProductBacklogController)
index() lists ALL product backlogs globally without organization filtering. edit() and update() use global slug lookups.

7. UserIssue Reassignment (UserIssueController)
Any authenticated user can change assigned users on any issue via update() which does Issue::slug($slug)->firstOrFail() then $issue->users()->sync($members).

8. Unauthenticated Attachment Upload
The attachments route group in routes/web.php does NOT include user.authenticated middleware, unlike all sibling route groups (comments, notes, labels, favorites).

9. Unauthenticated API Config Status Update
POST /api/config-status/update-position has no authentication. Any unauthenticated user can reorder config status columns.

10. Mass Assignment user_id Spoofing
Multiple models have user_id in $fillable, and controllers use $request->all() for create() and update(). Model observers only set user_id if not already present, allowing user impersonation.

Root Cause

The application lacks any authorization framework. The only security check is the UserAuthenticated middleware which verifies login status. CSRF protection middleware (VerifyCsrfToken) is also not registered in the web middleware group.

Recommended Fix

  1. Add Laravel Policies or Gates for all resource types
  2. Scope all resource lookups by organization membership
  3. Register VerifyCsrfToken middleware in the web middleware group
  4. Add user.authenticated middleware to the attachments route group
  5. Add authentication to API routes
  6. Remove user_id from $fillable on models, or use $request->validated() instead of $request->all()

Environment

  • Version: Latest (commit at time of audit)
  • Identified via: Static code analysis

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions