Skip to content

[Security][Medium] Email case-sensitivity inconsistency in GET /projects/:projectId denies access to legitimate shared users #70

@bmersereau

Description

@bmersereau

Severity: Medium

File: backend/src/routes/projects.ts:124-130
CWE: CWE-284 — Improper Access Control
OWASP: A01:2021 — Broken Access Control

Description

GET /projects/:projectId uses a case-sensitive Array.includes() check to verify project membership, while checkProjectAccess() (used everywhere else) performs a case-insensitive comparison:

// projects.ts:124 — case-SENSITIVE (bug):
const canAccess = project.user_id === userId ||
    (userEmail && Array.isArray(project.shared_with) &&
     project.shared_with.includes(userEmail));  // ← case-sensitive

// access.ts — case-INSENSITIVE (correct):
if (email && sharedWith.some((e) => (e ?? "").toLowerCase() === email)) {

Additionally, POST /projects stores shared_with without normalizing email casing, so mixed-case values can be written to the database.

Impact

A legitimate shared user whose email was stored with different casing (e.g., invited as "Alice@Company.Com" but authenticating as "alice@company.com") is incorrectly denied access on this specific GET endpoint. This is an access denial bug, not an access elevation — it does not grant unauthorized access.

Fix

1. Normalize shared_with on project creation (POST /projects):

const seen = new Set<string>();
const normalizedSharedWith: string[] = [];
for (const raw of Array.isArray(shared_with) ? shared_with : []) {
    if (typeof raw !== "string") continue;
    const e = raw.trim().toLowerCase();
    if (!e || seen.has(e)) continue;
    seen.add(e);
    normalizedSharedWith.push(e);
}

2. Replace the inline includes() check with checkProjectAccess() in GET /projects/:projectId:

const access = await checkProjectAccess(projectId, userId, userEmail, db);
if (!access.ok)
    return void res.status(404).json({ detail: "Project not found" });

Remediation tier: Backlog — schedule within 90 days.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions