Problem
/api/tasks/stats is registered with no middleware at all:
// stats — public aggregate endpoint, no auth required
app.route('/api/tasks/stats').get(tasks.stats);
The controller calls Task.estimatedDocumentCount() which returns a global count across all organizations — no scoping, no authentication.
This is inconsistent with other stats endpoints in the stack (e.g. scraps stats) which require JWT + organization scope.
Expected behaviour
Stats endpoints should be either:
- JWT-protected and org-scoped —
countDocuments({ organizationId }) like scraps stats, OR
- Intentionally public — document the reason explicitly and ensure the returned data cannot leak cross-org information
Fix
Option A (recommended): align with scraps stats pattern
app.route('/api/tasks/stats')
.all(passport.authenticate('jwt', { session: false }), organization.resolveOrganization, policy.isAllowed)
.get(tasks.stats);
And scope the repository query to organization._id.
Option B: if public is intentional, add policy.isAllowed at minimum and return only a safe aggregate (no org-specific data).
Migration
Check MIGRATIONS.md — any downstream project relying on unauthenticated access to /api/tasks/stats will need to pass a JWT after this change.
Problem
/api/tasks/statsis registered with no middleware at all:The controller calls
Task.estimatedDocumentCount()which returns a global count across all organizations — no scoping, no authentication.This is inconsistent with other stats endpoints in the stack (e.g. scraps stats) which require JWT + organization scope.
Expected behaviour
Stats endpoints should be either:
countDocuments({ organizationId })like scraps stats, ORFix
Option A (recommended): align with scraps stats pattern
And scope the repository query to
organization._id.Option B: if public is intentional, add
policy.isAllowedat minimum and return only a safe aggregate (no org-specific data).Migration
Check
MIGRATIONS.md— any downstream project relying on unauthenticated access to/api/tasks/statswill need to pass a JWT after this change.