Group-level vulnerability reports do not show all of the vulnerabilities belonging to the project that have been moved from other groups.

Summary

Group-level vulnerability reports do not show vulnerabilities detected before the project under that group is moved from another group.

Related slack thread(GitLab internal)

Zendesk ticket(gitLab internal)

Steps to reproduce

  1. There are groupA and groupB
  2. The projectA is created under the groupA. (groupA/projectA)
  3. Vulnerabilities are detected on the projectA
  4. The projectA is transferred to under the groupB. (groupB/projectA)
  5. Check the group-level vulnerability reports for groupB

Example Project

This contains customer information, so I'll leave it as the internal comment.

What is the current bug behavior?

The bug is ongoing. Customers can't see all vulnerabilities in the group-level report.

What is the expected correct behavior?

Group-level vulnerabilities report shows all vulnerabilities belonging to the project under the group no matter when it is detected.

Relevant logs and/or screenshots

This contains customer information, so I'll leave it as the internal comment.

Output of checks

This bug happens on GitLab.com /label reproduced on GitLab.com

Results of GitLab environment info

Expand for output related to GitLab environment info

(For installations with omnibus-gitlab package run and paste the output of:
`sudo gitlab-rake gitlab:env:info`)

(For installations from source run and paste the output of:
`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)

Results of GitLab application Check

N/A

Possible fixes

Testing locally, the group vulnerability GraphQL endpoint first queries a list of namespaces with type Group for the given group name.

Expand to see query
SELECT
    "namespaces"."id" AS t0_r0,
    "namespaces"."name" AS t0_r1,
    "namespaces"."path" AS t0_r2,
    "namespaces"."owner_id" AS t0_r3,
    "namespaces"."created_at" AS t0_r4,
    "namespaces"."updated_at" AS t0_r5,
    "namespaces"."type" AS t0_r6,
    "namespaces"."description" AS t0_r7,
    "namespaces"."avatar" AS t0_r8,
    "namespaces"."membership_lock" AS t0_r9,
    "namespaces"."share_with_group_lock" AS t0_r10,
    "namespaces"."visibility_level" AS t0_r11,
    "namespaces"."request_access_enabled" AS t0_r12,
    "namespaces"."ldap_sync_status" AS t0_r13,
    "namespaces"."ldap_sync_error" AS t0_r14,
    "namespaces"."ldap_sync_last_update_at" AS t0_r15,
    "namespaces"."ldap_sync_last_successful_update_at" AS t0_r16,
    "namespaces"."ldap_sync_last_sync_at" AS t0_r17,
    "namespaces"."description_html" AS t0_r18,
    "namespaces"."lfs_enabled" AS t0_r19,
    "namespaces"."parent_id" AS t0_r20,
    "namespaces"."shared_runners_minutes_limit" AS t0_r21,
    "namespaces"."repository_size_limit" AS t0_r22,
    "namespaces"."require_two_factor_authentication" AS t0_r23,
    "namespaces"."two_factor_grace_period" AS t0_r24,
    "namespaces"."cached_markdown_version" AS t0_r25,
    "namespaces"."project_creation_level" AS t0_r26,
    "namespaces"."runners_token" AS t0_r27,
    "namespaces"."file_template_project_id" AS t0_r28,
    "namespaces"."saml_discovery_token" AS t0_r29,
    "namespaces"."runners_token_encrypted" AS t0_r30,
    "namespaces"."custom_project_templates_group_id" AS t0_r31,
    "namespaces"."auto_devops_enabled" AS t0_r32,
    "namespaces"."extra_shared_runners_minutes_limit" AS t0_r33,
    "namespaces"."last_ci_minutes_notification_at" AS t0_r34,
    "namespaces"."last_ci_minutes_usage_notification_level" AS t0_r35,
    "namespaces"."subgroup_creation_level" AS t0_r36,
    "namespaces"."emails_disabled" AS t0_r37,
    "namespaces"."max_pages_size" AS t0_r38,
    "namespaces"."max_artifacts_size" AS t0_r39,
    "namespaces"."mentions_disabled" AS t0_r40,
    "namespaces"."default_branch_protection" AS t0_r41,
    "namespaces"."unlock_membership_to_ldap" AS t0_r42,
    "namespaces"."max_personal_access_token_lifetime" AS t0_r43,
    "namespaces"."push_rule_id" AS t0_r44,
    "namespaces"."shared_runners_enabled" AS t0_r45,
    "namespaces"."allow_descendants_override_disabled_shared_runners" AS t0_r46,
    "namespaces"."traversal_ids" AS t0_r47,
    "namespaces"."organization_id" AS t0_r48,
    "routes"."id" AS t1_r0,
    "routes"."source_id" AS t1_r1,
    "routes"."source_type" AS t1_r2,
    "routes"."path" AS t1_r3,
    "routes"."created_at" AS t1_r4,
    "routes"."updated_at" AS t1_r5,
    "routes"."name" AS t1_r6,
    "routes"."namespace_id" AS t1_r7
FROM
    "namespaces"
    LEFT OUTER JOIN "routes" ON "routes"."source_type" = 'Namespace'
    AND "routes"."source_id" = "namespaces"."id"
WHERE
    "namespaces"."type" = 'Group'
    AND ((LOWER(routes.path) = LOWER('gitlab-org'))) 

Then queries all vulnerability_reads for the given group namespaces:

Expand to see query
 SELECT
    "vulnerability_reads".*
FROM
    "vulnerability_reads"
WHERE
    "vulnerability_reads"."vulnerability_id" IN (
        SELECT
            "vulnerability_reads"."vulnerability_id"
        FROM
            unnest(ARRAY (
                    SELECT
                        "namespaces"."id" FROM "namespaces"
                    WHERE
                        "namespaces"."type" = 'Group'
                        AND (traversal_ids @> ('{24}')))::bigint[]) AS "namespace_ids" ("namespace_id"), unnest('{6,2,5,3,1,0,4,99}'::smallint[]) AS "report_types" ("report_type"), unnest('{1,4}'::smallint[]) AS "states" ("state"), LATERAL (
                SELECT
                    "vulnerability_reads"."namespace_id", "vulnerability_reads"."report_type", "vulnerability_reads"."state", "vulnerability_reads"."severity", "vulnerability_reads"."vulnerability_id"
                FROM
                    "vulnerability_reads"
                WHERE (vulnerability_reads. "namespace_id" = "namespace_ids"."namespace_id")
                AND (vulnerability_reads. "report_type" = "report_types"."report_type")
                AND (vulnerability_reads. "state" = "states"."state")
            ORDER BY
                "vulnerability_reads"."severity" DESC,
                "vulnerability_reads"."vulnerability_id" DESC
            LIMIT 21) AS vulnerability_reads
    ORDER BY
        "vulnerability_reads"."severity" DESC,
        "vulnerability_reads"."vulnerability_id" DESC
    LIMIT 21)
ORDER BY
    "vulnerability_reads"."severity" DESC,
    "vulnerability_reads"."vulnerability_id" DESC
LIMIT 21

It seems likely that that namespaces associated with the vulnerability_reads are not being updated when a project is moved from one group to another.

If that's the case a possible fix is:

  • Update ee/app/services/ee/projects/transfer_service.rb and add a hook to update the vulnerability_reads#namespace_id values to the new group when transferring a project. This should be performed in batches.
  • Write a migration to find and fix up 'orphan' vulnerability_reads - i.e. like those in this bug report - and repoint them to the correct group.

For the record, the namespace_id on vulnerability_reads is initially set via a trigger here

Edited by Malcolm Locke