Skip to content

Security: Cross-user dashboard entry modification in UpdateTag/RemoveTag (missing user filter) #238

@lighthousekeeper1212

Description

@lighthousekeeper1212

Summary

When renaming or deleting a tag, the dashboard entry scan queries ALL users' dashboard entries without filtering by user_id. This allows cross-user data modification (UpdateTag) and cross-user information disclosure (RemoveTag).

Details

UpdateTag - Cross-user data modification (HIGH)

File: tag/update.go lines 53-73

When renaming a tag, the code searches ALL dashboard entries matching the tag key, then modifies them:

// Lines 53-59: Searches ALL entries, not just current user's
if err := tx.Where("keys LIKE ?", "%"+key).
    Or("keys like ?", "%"+key+"%").
    Or("keys like ?", key+"%").
    Find(&usedInEntries).Error; err != nil {

// Lines 61-73: MODIFIES entries from any user
for _, entry := range usedInEntries {
    tags := strings.Split(entry.Keys, ",")
    for index, tagInEntry := range tags {
        if tagInEntry == key {
            tags[index] = *newKey  // Modifies other users' entries!
        }
    }
    entry.Keys = strings.Join(tags, ",")
    tx.Save(&entry)  // Writes to DB
}

RemoveTag - Cross-user info disclosure (LOW)

File: tag/remove.go lines 23-33

When trying to delete a tag, the code checks ALL entries and leaks another user's dashboard name:

if len(usedInEntries) > 0 {
    dashboard := &model.Dashboard{ID: usedInEntries[0].DashboardID}
    r.DB.Find(dashboard)
    return nil, fmt.Errorf("tag '%s' is used in dashboard '%s' entry '%s'...", key, dashboard.Name, ...)
    // Leaks another user's dashboard name and entry title
}

Secure comparison

TimeSpan tag updates (same file, lines 37-49) correctly scope to user:

timeSpansIdsOfUser := tx.Model(new(model.TimeSpan)).
    Select("id").
    Where(&model.TimeSpan{UserID: userID}).  // ✓ Scoped to user
    SubQuery()

Impact

If two users create tags with the same key (e.g., "project"), User A renaming their tag will silently modify User B's dashboard entries. User A deleting their tag may also be blocked by User B's entries and leak User B's dashboard name.

Recommended Fix

Join to the dashboards table and filter by user_id:

if err := tx.
    Joins("INNER JOIN dashboards ON dashboard_entries.dashboard_id = dashboards.id").
    Where("dashboards.user_id = ?", userID).
    Where("keys LIKE ? OR keys LIKE ? OR keys LIKE ?", "%"+key, "%"+key+"%", key+"%").
    Find(&usedInEntries).Error; err != nil {

Metadata

Metadata

Assignees

No one assigned

    Labels

    a:bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions