#3689·harness

[Security] ListAllGitspaces Authorization Bypass

Author: geo-chenCreated May 19, 2026Updated May 19, 2026

Summary

The GET /api/v1/gitspaces endpoint fails to enforce space-level authorization when listing a user's gitspace configurations. The getAuthorizedSpaces() function calls CheckGitspace() for each space but always marks the space as authorized regardless of the check result, because the missing continue in the "no access" branch causes authorizedSpaceIDs[spaceID] = true to execute unconditionally. An authenticated user who has been removed from a space can still retrieve their gitspace configurations (including access keys and SSH commands) from that space.

Vulnerability Details

app/api/controller/gitspace/list_all.go, lines 127–145:

go
func (c *Controller) getAuthorizedSpaces(
    ctx context.Context,
    session *auth.Session,
    spacesMap map[int64]string,
) (map[int64]bool, error) {
    var authorizedSpaceIDs = make(map[int64]bool, 0)

    for spaceID, spacePath := range spacesMap {
        err := apiauth.CheckGitspace(
            ctx, c.authorizer, session, spacePath, "", enum.PermissionGitspaceView,
        )
        if err != nil && !apiauth.IsNoAccess(err) {  // line 138
            return nil, fmt.Errorf("failed to check gitspace auth for space ID %d: %w", spaceID, err)
        }
        // BUG: no `continue` here — falls through when err is ErrForbidden/ErrUnauthorized
        authorizedSpaceIDs[spaceID] = true            // line 142 — executes unconditionally
    }

    return authorizedSpaceIDs, nil
}

apiauth.IsNoAccess(err) returns true when err is ErrForbidden or ErrUnauthorized (app/api/auth/auth.go:90). When CheckGitspace denies access:

  • err != nil → true
  • !apiauth.IsNoAccess(err) → false (it IS a no-access error)
  • Combined condition → false → does NOT return an error
  • Execution falls through to authorizedSpaceIDs[spaceID] = true

The space is marked authorized in all cases — authorization check has no effect.

Impact

An authenticated user who has been removed from a Gitness space can issue:

GET /api/v1/gitspaces
Authorization: Bearer <user_token>

The response returns their gitspace configurations from all spaces, including spaces they no longer have gitspace_view permission for. Each returned GitspaceConfig may include:

  • GitspaceInstance.AccessKey — access credential for connecting to the running environment
  • GitspaceInstance.SSHCommand — full SSH connection command
  • GitspaceInstance.URL — accessible endpoint URL
  • GitspaceConfig.SSHTokenIdentifier — reference to stored SSH token
  • code_repo_url / code_repo_is_private — private repository details
  • space_path — path of the unauthorized space

If a gitspace instance is still running after the user's space membership is revoked, the exposed AccessKey may allow the user to maintain access to the development environment beyond the intended revocation point.

PoC

bash
# Step 1: User registers and is added to a space, creates a gitspace there
# Step 2: Admin removes user from the space (revokes membership)
# Step 3: User calls the listing endpoint with their token

curl -s -H "Authorization: Bearer <USER_TOKEN>" \
  https://<gitness-host>/api/v1/gitspaces | jq .

# Expected: empty array or only gitspaces from authorized spaces
# Actual: all gitspace configs including from the revoked space,
#         with AccessKey, SSHCommand, and URL populated

Severity: Medium (CVSS 4.3)
CVSS Vector: AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
CWE: CWE-285 (Improper Authorization)
Affected Version: main branch (commit checked: latest as of 2026-05-19)