Repo information leaked when repo access is set to project members only

HackerOne report #460344 by xanbanx on 2018-12-11:

Summary

GitLab allows to set the repository permissions of a project to Only Project Members. This prevents unauthorized users to gain access to repository information. However, there is some repository information, which is leaked via the project API. In particular, non-project members of public projects have access to:

  1. The default branch
  2. The commit statistics

Both information is available via the V4 REST API. The default branch is also available via the GraphQL API.

Steps To Reproduce:

Reproduced on GitLab 11.6.0-rc4-ee

  1. Create a public project (in this case name test-default-branch) and set the repository access to Only Project Members
  2. Login as a second user, which is not a member of the project
  3. With the second user, perform the following API query (substitute the project-id with the ID of the project created in step 1):
curl --header "PRIVATE-TOKEN: <your-PAT>" "https://gitlab.example.com/api/v4/projects/<project-id>?statistics=true"

This will return following JSON response:

{
    "id": 17,
    "description": "",
    "name": "test-repo",
    "name_with_namespace": "xanbanx / test-repo",
    "path": "test-repo",
    "path_with_namespace": "xanbanx/test-repo",
    "created_at": "2018-12-11T11:25:29.011Z",
    "default_branch": "master",
    "tag_list": [],
    "ssh_url_to_repo": "git@example.gitlab.com:xanbanx/test-repo.git",
    "http_url_to_repo": "https://example.gitlab.com/xanbanx/test-repo.git",
    "web_url": "https://example.gitlab.com/xanbanx/test-repo",
    "readme_url": null,
    "avatar_url": null,
    "star_count": 0,
    "forks_count": 0,
    "last_activity_at": "2018-12-11T11:25:29.011Z",
    "namespace": {
        "id": 13,
        "name": "xanbanx",
        "path": "xanbanx",
        "kind": "user",
        "full_path": "xanbanx",
        "parent_id": null
    },
    "_links": {
        "self": "https://example.gitlab.com/api/v4/projects/17",
        "repo_branches": "https://example.gitlab.com/api/v4/projects/17/repository/branches",
        "labels": "https://example.gitlab.com/api/v4/projects/17/labels",
        "events": "https://example.gitlab.com/api/v4/projects/17/events",
        "members": "https://example.gitlab.com/api/v4/projects/17/members"
    },
    "archived": false,
    "visibility": "public",
    "owner": {
        "id": 3017950,
        "name": "xanbanx",
        "username": "xanbanx",
        "state": "active",
        "avatar_url": "https://secure.gravatar.com/avatar/6466f73ed21b9d1624dee906921e9176?s=80&d=identicon",
        "web_url": "https://example.gitlab.com/xanbanx"
    },
    "resolve_outdated_diff_discussions": false,
    "container_registry_enabled": true,
    "issues_enabled": false,
    "merge_requests_enabled": false,
    "wiki_enabled": false,
    "jobs_enabled": false,
    "snippets_enabled": false,
    "shared_runners_enabled": true,
    "lfs_enabled": true,
    "creator_id": 3017950,
    "import_status": "none",
    "public_jobs": true,
    "ci_config_path": null,
    "shared_with_groups": [],
    "only_allow_merge_if_pipeline_succeeds": false,
    "request_access_enabled": false,
    "only_allow_merge_if_all_discussions_are_resolved": false,
    "printing_merge_request_link_enabled": true,
    "merge_method": "merge",
    "statistics": {
        "commit_count": 1,
        "storage_size": 83886,
        "repository_size": 83886,
        "lfs_objects_size": 0,
        "job_artifacts_size": 0
    },
    "permissions": {
        "project_access": null,
        "group_access": null
    },
    "approvals_before_merge": 0,
    "mirror": false,
    "external_authorization_classification_label": "",
    "packages_enabled": true
}

This includes the statistics, showing the repo size and number of commits. Furthermore, the default branch is shown. Both information should be private then the repo permission is set to `Project Members Only.

Accessing the default branch is also possible via the new GraphQL API. Therefore, perform the following query (substitute the fullPath with the path of the project created in step 1):

{
  project(fullPath: "xanbanx/test-repo") {
    id
    name
    defaultBranch
  }
}

This will return the following response including the default branch:

{
  "data": {
    "project": {
      "id": "17",
      "name": "test-repo",
      "defaultBranch": "master"
    }
  }
}

Steps to mitigate

When repo access is set to Project Members Only, do not show repository related information. Instead leave them blank with a null value,

Impact

Any user can see the default branch and the commit statistics of the repo although the user does not have repository access.