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
- Add Laravel Policies or Gates for all resource types
- Scope all resource lookups by organization membership
- Register
VerifyCsrfToken middleware in the web middleware group
- Add
user.authenticated middleware to the attachments route group
- Add authentication to API routes
- 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
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:
While
CommentController::destroy()correctly uses->userActive()->firstOrFail().2. Comment Edit Without Ownership (CommentController line 16-25)
edit()uses bareComment::find($id)without ownership check. The same controller'sdestroy()andupdate()correctly use->userActive()->firstOrFail(). Classic 1-of-3 inconsistency.3. Sprint CRUD Without Organization Check (SprintController)
edit(),update(),destroy(),statusUpdate()all useSprint::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 globalIssue::slug($slug)lookups. TheIssueMiddlewarethat 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()andupdate()use global slug lookups.7. UserIssue Reassignment (UserIssueController)
Any authenticated user can change assigned users on any issue via
update()which doesIssue::slug($slug)->firstOrFail()then$issue->users()->sync($members).8. Unauthenticated Attachment Upload
The
attachmentsroute group inroutes/web.phpdoes NOT includeuser.authenticatedmiddleware, unlike all sibling route groups (comments, notes, labels, favorites).9. Unauthenticated API Config Status Update
POST /api/config-status/update-positionhas no authentication. Any unauthenticated user can reorder config status columns.10. Mass Assignment user_id Spoofing
Multiple models have
user_idin$fillable, and controllers use$request->all()forcreate()andupdate(). Model observers only setuser_idif not already present, allowing user impersonation.Root Cause
The application lacks any authorization framework. The only security check is the
UserAuthenticatedmiddleware which verifies login status. CSRF protection middleware (VerifyCsrfToken) is also not registered in the web middleware group.Recommended Fix
VerifyCsrfTokenmiddleware in the web middleware groupuser.authenticatedmiddleware to the attachments route groupuser_idfrom$fillableon models, or use$request->validated()instead of$request->all()Environment