CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-FGXM-5HVV-X4VH

GHSA-fgxm-5hvv-x4vh: Private Repository Enumeration via Go Module Discovery and HTTP Response Discrepancies in Soft Serve

Alon Barad
Alon Barad
Software Engineer

Oct 30, 2026·12 min read·3 visits

Executive Summary (TL;DR)

Soft Serve versions 0.5.0 to 0.12.2 disclose private repository names and clone URLs over HTTP. An authorization bypass in the ?go-get=1 handler allows unauthenticated or low-privileged users to resolve private repository metadata via sub-path queries. Additionally, inconsistent HTTP status codes (401/403 vs. 404) across Git and LFS endpoints act as an oracle for repository existence. Upgrading to v0.12.3 resolves these issues.

Charmbracelet Soft Serve versions 0.5.0 through 0.12.2 contain an authorization bypass and observable response discrepancy vulnerability in the HTTP Git server. Remote attackers can determine the existence, exact names, and clone URLs of private repositories by issuing crafted Go remote import path discovery requests (?go-get=1) or by observing differential HTTP status codes across Git Smart HTTP, Dumb HTTP, and Git LFS endpoints.

Vulnerability Overview

Soft Serve (github.com/charmbracelet/soft-serve) is a self-hostable Git server managed via the command line and SSH, providing Git repository access over SSH, Git daemon, and HTTP/HTTPS protocols. In addition to standard Git operations and Git Large File Storage (LFS), Soft Serve's HTTP server implements the Go remote import path protocol. When the Go toolchain executes commands such as go get, it appends the ?go-get=1 query parameter to the requested package path, expecting the server to return an HTML response containing <meta name="go-import"> tags that identify the root repository URL.

In Soft Serve versions 0.5.0 through 0.12.2, the HTTP request routing and access control middleware fail to properly enforce read authorization when resolving repository paths. Specifically, the vulnerability manifests through two distinct mechanisms: a logic flaw in how ?go-get=1 sub-path requests are evaluated by the access control middleware and the Go import handler (CWE-285: Improper Authorization), and an observable response discrepancy across Git Smart HTTP, Dumb HTTP, and LFS endpoints (CWE-203: Observable Discrepancy).

Through these flaws, an unauthorized actor with network access to the Soft Serve HTTP interface (default port :23231) can enumerate private repositories. Under the default server configuration (anon-access: read-only), an unauthenticated remote client can confirm whether specific private repositories exist and extract their canonical names and clone URLs. Even when anonymous access is disabled (anon-access: no-access), any authenticated user with low privileges can perform the same enumeration against private repositories belonging to other users or teams.

Furthermore, because the ?go-get=1 bypass condition was originally implemented inside the shared withAccess HTTP middleware rather than scoped exclusively to the Go module discovery route, appending ?go-get=1 to non-existent repository paths on Git LFS basic transfer endpoints bypasses nil-repository guards. This triggers a nil-pointer dereference in the underlying LFS handler, resulting in an unhandled panic that is caught by the HTTP recovery middleware and returned as an HTTP 500 Internal Server Error.

Root Cause Analysis

The primary information disclosure mechanism stems from an interaction between two components in pkg/web: the withAccess middleware in pkg/web/git.go and the GoGetHandler function in pkg/web/goget.go. When an HTTP request arrives, withAccess resolves the target repository from the full URL path and evaluates the caller's permissions via be.AccessLevelForUser(ctx, repoName, user).

Because Soft Serve supports automatic repository creation upon push ("push-to-create"), calling AccessLevelForUser on a path string that does not correspond to an existing repository returns the server's default baseline access level rather than NoAccess. Under the default anon-access: read-only setting, any non-existent path evaluates to ReadOnlyAccess for anonymous callers. If a caller is authenticated, non-existent paths evaluate to ReadWriteAccess. In withAccess, the switch statement checked case r.URL.Query().Get("go-get") == "1" && accessLevel >= access.ReadOnlyAccess: before checking case repo == nil, accessLevel < access.ReadOnlyAccess:. When a user requested /secret/subpkg?go-get=1 (where secret is a private repository and secret/subpkg does not exist), repo was nil, accessLevel evaluated to ReadOnlyAccess, and the middleware permitted the request to pass through to GoGetHandler.

Once execution reached GoGetHandler, the handler walked up the URL path directory by directory using path.Dir(repo) until it found a matching repository via be.Repository(ctx, repo). The loop terminated as soon as be.Repository returned no error, meaning a valid repository object was located. Crucially, GoGetHandler assumed withAccess had already authorized the request and never called be.AccessLevelForUser on the resolved parent repository. It immediately rendered the HTTP 200 OK template containing the <meta name="go-import"> tag for /secret.

The secondary vulnerability mechanism involves status code discrepancies across standard Git HTTP and LFS routes. When withAccess processed standard Git Smart HTTP requests without ?go-get=1, it checked whether repo == nil before evaluating user credentials or authorization. If a repository did not exist, the server immediately returned 404 Not Found. If the repository existed and was private, the server checked credentials and returned 401 Unauthorized (with a WWW-Authenticate header) to anonymous users.

A similar discrepancy existed in Git LFS write routes and JWT authentication (pkg/web/auth.go). On LFS upload endpoints, write-access checks occurred before verifying whether the user had basic read visibility into the repository; an authenticated user without access received 403 Forbidden for an existing private repository, but 404 Not Found for a missing repository. Additionally, during JWT validation in parseJWT, querying a non-existent repository returned a generic "missing repository" error that downgraded the caller to an anonymous context, whereas supplying an invalid or mismatched token against an existing private repository returned ErrInvalidToken, yielding 403 Forbidden.

Code Analysis

The vulnerabilities were addressed across two commits in version v0.12.3. The first commit (74e41543304d1e12fd54fc59e410bd428373dd34) refactored the go-get routing and access checks. Previously, withAccess in pkg/web/git.go contained a special pass-through case for go-get=1, and GoGetHandler performed unauthenticated path walking:

// Vulnerable code in pkg/web/git.go (prior to v0.12.3)
switch {
case r.URL.Query().Get("go-get") == "1" && accessLevel >= access.ReadOnlyAccess:
    // FLAW: When querying /private-repo/subpath?go-get=1, repo is nil,
    // but accessLevel for the non-existent subpath is ReadOnlyAccess.
    // This breaks out of the switch before the repo == nil check below.
    break
case repo == nil, accessLevel < access.ReadOnlyAccess:
    renderNotFound(w, r)
    return
}
// Vulnerable code in pkg/web/goget.go (prior to v0.12.3)
for {
    // FLAW: Checks only if the parent path is a valid repository,
    // completely omitting authorization checks on the resolved repo.
    if _, err := be.Repository(ctx, repo); err == nil {
        break
    }
    repo = path.Dir(repo)
    if repo == "/" || repo == "." {
        renderNotFound(w, r)
        return
    }
}

In commit 74e41543304d1e12fd54fc59e410bd428373dd34, the maintainer removed the go-get=1 case from withAccess and replaced the middleware wrapper on the go-get route with withCaller, which extracts user authentication state without performing premature target authorization. Authorization is now enforced directly inside GoGetHandler at each step of the directory traversal, and defensive nil checks were added to the LFS handlers in pkg/web/git_lfs.go:

// Patched code in pkg/web/goget.go (Commit 74e41543304d1e12fd54fc59e410bd428373dd34)
user := proto.UserFromContext(ctx)
for {
    // FIX: Verify both repository existence AND caller read authorization
    if _, err := be.Repository(ctx, repo); err == nil && be.AccessLevelForUser(ctx, repo, user) >= access.ReadOnlyAccess {
        break
    }
    repo = path.Dir(repo)
    if repo == "/" || repo == "." {
        // Uses unified denyRead helper in subsequent commit
        denyRead(w, r, authErr, false)
        return
    }
}

The second commit (dc351aa0d824c8bef528c6924a7acdfb9f20c852) eliminated the HTTP status code discrepancy oracles by introducing a centralized denyRead helper in pkg/web/git.go and modifying parseJWT in pkg/web/auth.go. Rather than branching on whether repo == nil prior to checking credentials, the patched server bases its rejection response strictly on the caller's authentication state:

// Patched code in pkg/web/git.go (Commit dc351aa0d824c8bef528c6924a7acdfb9f20c852)
func denyRead(w http.ResponseWriter, r *http.Request, err error, lfsJSON bool) {
    switch {
    case badCredentials(err) && lfsJSON:
        renderJSON(w, http.StatusForbidden, lfs.ErrorResponse{Message: "bad credentials"})
    case badCredentials(err):
        renderForbidden(w, r)
    case proto.UserFromContext(r.Context()) == nil && lfsJSON:
        askCredentials(w, r)
        renderJSON(w, http.StatusUnauthorized, lfs.ErrorResponse{Message: "credentials needed"})
    case proto.UserFromContext(r.Context()) == nil:
        // FIX: Anonymous callers receive 401 Unauthorized for BOTH missing and private repos
        askCredentials(w, r)
        renderUnauthorized(w, r)
    case lfsJSON:
        renderJSON(w, http.StatusNotFound, lfs.ErrorResponse{Message: "repository not found"})
    default:
        // FIX: Authenticated callers without access receive 404 Not Found for BOTH missing and private repos
        renderNotFound(w, r)
    }
}

The fix is comprehensive with respect to HTTP response codes and body content. By unifying the error responses across withAccess, GoGetHandler, and parseJWT, an external observer can no longer distinguish between a non-existent repository and an unauthorized private repository via HTTP status codes or headers. The only remaining avenue for repository name discovery over HTTP/SSH is confined to authenticated users who explicitly hold repository creation (ReadWriteAccess on non-existent paths) privileges attempting to push a new repository, which is an inherent property of push-to-create architectures.

Exploitation

Exploiting GHSA-fgxm-5hvv-x4vh requires only network access to the Soft Serve HTTP port and a standard HTTP client such as curl. No specialized tooling or complex state manipulation is necessary. Under the default configuration (anon-access: read-only), an unauthenticated attacker can probe for a suspected private repository named internal-billing using two techniques.

In the first technique, the attacker leverages the ?go-get=1 sub-path traversal flaw. If the attacker queries the exact repository path /internal-billing?go-get=1, withAccess resolves the repository object, sees that it is private, and blocks the request. However, if the attacker appends an arbitrary non-existent child segment (such as /internal-billing/pkg?go-get=1), withAccess evaluates internal-billing/pkg, finds no matching repository object, assigns the default ReadOnlyAccess level, and forwards the request to GoGetHandler:

# Querying the root path directly fails authorization
curl -i "http://target-host:23231/internal-billing?go-get=1"
# HTTP/1.1 401 Unauthorized (or 404 Not Found)
 
# Querying any arbitrary sub-path bypasses withAccess and triggers GoGetHandler traversal
curl -s "http://target-host:23231/internal-billing/x?go-get=1" | grep go-import
# <meta name="go-import" content="target-host:23231/internal-billing git http://target-host:23231/internal-billing.git">

Furthermore, if the server hosts nested private repositories (for example, org/project-alpha), an attacker who knows only the top-level prefix can query /org/project-alpha/x?go-get=1 to confirm org/project-alpha, or query /org/nonexistent?go-get=1 to discover if /org itself is a private repository. Because GoGetHandler walks up the entire path hierarchy until it hits /, any private parent repository along the path will be disclosed if the leaf path does not exist.

In the second technique, the attacker utilizes the status code discrepancy on standard Git Smart HTTP endpoints (such as /info/refs?service=git-upload-pack) to perform high-speed dictionary enumeration without relying on go-get:

# Probing a non-existent repository returns 404 Not Found
curl -o /dev/null -s -w "%{http_code}\n" "http://target-host:23231/nonexistent-repo.git/info/refs?service=git-upload-pack"
# Output: 404
 
# Probing an existing private repository returns 401 Unauthorized
curl -o /dev/null -s -w "%{http_code}\n" "http://target-host:23231/internal-billing.git/info/refs?service=git-upload-pack"
# Output: 401

> [!NOTE] > If an attacker sends a request to a Git LFS basic transfer endpoint for a non-existent repository while appending ?go-get=1 (e.g., GET /nonexistent.git/info/lfs/objects/basic/oid?go-get=1), the request bypasses the repo == nil guard in withAccess and causes a nil-pointer dereference panic when repo.ID() is invoked in serviceLfsBasicDownload, returning an HTTP 500 Internal Server Error.

Impact Assessment

The vulnerability is rated Medium severity with a CVSS v3.1 base score of 5.3 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N). The Attack Vector is Network (AV:N) and Attack Complexity is Low (AC:L), as the HTTP interface is directly reachable by remote clients and requires no special timing or state preparation. Privileges Required is None (PR:N) in default deployments where anon-access is set to read-only, and User Interaction is None (UI:N).

The Confidentiality Impact is Low (C:L). An attacker cannot read repository file contents, commit history, branches, or Git blobs through this flaw. However, the attacker gains reliable confirmation of private repository existence, exact repository path names, and canonical clone URLs. In enterprise and software development environments, repository names frequently contain sensitive metadata, including unannounced product codenames, customer names, zero-day research identifiers, or internal infrastructure component mappings.

There is no direct impact to Integrity (I:N) or Availability (A:N). While appending ?go-get=1 to Git LFS basic transfer endpoints triggers a nil-pointer dereference panic in serviceLfsBasicDownload and serviceLfsBasicUpload, Soft Serve wraps its HTTP router in recovery middleware that catches the panic on the active goroutine and returns an HTTP 500 response. Consequently, the panic does not crash the Soft Serve daemon process or cause a persistent denial of service.

At the time of publication, there is no CVE ID assigned to GHSA-fgxm-5hvv-x4vh, and it is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog. No public exploit kits or automated scanning templates (such as Nuclei) have been published in major exploit repositories, though trivial enumeration via standard HTTP clients remains straightforward to automate.

Remediation

To fully remediate this vulnerability, administrators and developers must upgrade Soft Serve (github.com/charmbracelet/soft-serve) to version v0.12.3 or later. Version v0.12.3 contains both commit 74e41543304d1e12fd54fc59e410bd428373dd34 and commit dc351aa0d824c8bef528c6924a7acdfb9f20c852, which enforce read permission checks during go-get directory traversal, add nil-pointer guards to LFS handlers, and standardize HTTP error responses across missing and private repositories.

For installations managed via Go modules or built from source, update the dependency or install the patched binary directly:

go install github.com/charmbracelet/soft-serve/cmd/soft@v0.12.3

If an immediate upgrade to v0.12.3 is not operationally feasible, administrators can apply configuration workarounds in the Soft Serve config.yaml file (located in the Soft Serve data directory) to reduce the attack surface:

  1. Disable the HTTP Server: If Git operations and Go module fetching over HTTP are not required, disable the HTTP listener entirely. This restricts all repository interactions to SSH, eliminating the vulnerable HTTP handlers:

    http:
      enabled: false
  2. Restrict Anonymous Access: If the HTTP server must remain enabled, configure anon-access to no-access (or none) in the server configuration or via the SSH administration CLI (soft settings anon-access no-access). While this prevents unauthenticated attackers from leveraging the ?go-get=1 sub-path traversal bypass, note that unauthenticated status code discrepancies on certain routes and enumeration by authenticated low-privileged users remain possible until the binary is upgraded to v0.12.3.

  3. Reverse Proxy Filtering: If Soft Serve sits behind a reverse proxy (such as Nginx, Caddy, or HAProxy) and Go remote import discovery is not used, configure the proxy to block or strip requests containing the go-get=1 query parameter to prevent the path traversal bypass and the LFS nil-pointer panic.

Official Patches

CharmbraceletSoft Serve v0.12.3 Official Release
CharmbraceletGitHub Security Advisory GHSA-fgxm-5hvv-x4vh

Fix Analysis (2)

Technical Appendix

CVSS Score
5.3/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

Affected Systems

github.com/charmbracelet/soft-serve

Affected Versions Detail

Product
Affected Versions
Fixed Version
Soft Serve (github.com/charmbracelet/soft-serve)
Charmbracelet
>= 0.5.0, < 0.12.30.12.3
AttributeDetail
Vulnerability IDGHSA-fgxm-5hvv-x4vh
Primary CWECWE-200 / CWE-203 / CWE-285
CVSS v3.1 Score5.3 Medium (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)
Attack VectorNetwork (HTTP Git / LFS / Go Import Handlers)
ImpactPrivate Repository Name & Clone URL Disclosure
Affected Versions>= 0.5.0, < 0.12.3
Patched Versionv0.12.3
Exploit StatusNo Public Exploits / Trivial via HTTP Client

MITRE ATT&CK Mapping

T1592Gather Victim Host Information
Reconnaissance
T1595.002Active Scanning: Vulnerability Scanning
Reconnaissance
T1083File and Directory Discovery
Discovery
T1087Account Discovery
Discovery
CWE-200
Exposure of Sensitive Information to an Unauthorized Actor

The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.

Vulnerability Timeline

Fix commit 74e41543304d1e12fd54fc59e410bd428373dd34 authored to enforce authorization in GoGetHandler and add nil checks in LFS handlers.
2026-10-02
Fix commit dc351aa0d824c8bef528c6924a7acdfb9f20c852 authored to unify HTTP responses across missing and private repositories.
2026-10-03
Soft Serve v0.12.3 released and GitHub Security Advisory GHSA-fgxm-5hvv-x4vh published.
2026-10-30

References & Sources

  • [1]GitHub Security Advisory: GHSA-fgxm-5hvv-x4vh
  • [2]Charmbracelet Soft Serve Release v0.12.3
  • [3]Fix Commit: stop go-get requests from revealing private repository names
  • [4]Fix Commit: answer the same for private and missing repositories over HTTP
  • [5]Charmbracelet Soft Serve Source Repository

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•less than a minute ago•CVE-2026-59978
7.5

CVE-2026-59978: Persistent Denial of Service via Array Injection in AdminNeo

AdminNeo versions 4.6.2 through 5.4.1 contain an improper input validation vulnerability (CWE-20) in the version verification endpoint (?script=version). An unauthenticated remote attacker can submit a crafted HTTP POST request containing an array parameter for 'version', which is serialized into a shared server-side temporary cache file without type checking or authentication. On subsequent requests, the application deserializes this cached array and passes it to cookie() and rawurlencode(), triggering an uncaught fatal TypeError in PHP 8.x environments and causing a persistent 24-hour Denial of Service (DoS) for all users.

Amit Schendel
Amit Schendel
0 views•8 min read
•about 2 hours ago•GHSA-F3FJ-9625-RV3M
5.9

GHSA-f3fj-9625-rv3m: Race Condition in Soft Serve Repository Cache Invalidation

A Time-of-Check to Time-of-Use (TOCTOU) race condition in Charmbracelet Soft Serve versions 0.6.0 through 0.12.2 allows unauthenticated users to retain read access to repositories after their visibility is changed from public to private. The flaw stems from premature cache invalidation in the SetPrivate method prior to committing the visibility update to the underlying database. Concurrent read requests arriving during the database transaction window repopulate the non-expiring in-memory LRU cache with stale public metadata, bypassing access controls across HTTP, SSH, and Git protocols until the server process is restarted.

Alon Barad
Alon Barad
3 views•10 min read
•about 3 hours ago•CVE-2026-119283
7.5

CVE-2026-119283: Unbounded JWE Recipients Array Causes CPU Exhaustion in Authlib joserfc

A resource consumption vulnerability exists in the Authlib joserfc Python library prior to version 1.7.5. When processing JSON Web Encryption (JWE) objects formatted using General JSON Serialization, the library fails to enforce an upper bound on the number of elements within the recipients array. An unauthenticated attacker can supply a JWE containing thousands of recipient entries targeting an RSA key management algorithm, forcing the server to perform an equivalent number of computationally expensive RSA private-key decryption operations per request and exhausting available CPU resources.

Alon Barad
Alon Barad
2 views•9 min read
•about 4 hours ago•CVE-2026-119284
6.9

CVE-2026-119284: Uncontrolled Resource Consumption in joserfc Compact Deserialization

CVE-2026-119284 (GHSA-9pg5-8c7x-5hfx) is an uncontrolled resource consumption vulnerability (CWE-400) in the compact serialization deserialization routines of joserfc prior to version 1.7.6. The library enforced individual segment size limits only after invoking Python's bytes.split(b'.') on untrusted input tokens. By sending oversized compact JWS or JWE tokens, an unauthenticated remote attacker can force the Python interpreter to duplicate and allocate memory for every segment before size validation executes, resulting in memory exhaustion and an Endpoint Denial of Service (DoS).

Amit Schendel
Amit Schendel
7 views•10 min read
•about 11 hours ago•GHSA-C33X-VCV4-VG3G
2.1

GHSA-c33x-vcv4-vg3g: Missing Slot Identifier Validation in Cedar Policy Language JSON Template Parser Causes Panic and Authorization Divergence

An improper input validation vulnerability (CWE-20) exists in the Entity Syntax Tree (EST) JSON deserialization component of the Cedar Policy Language (`cedar-policy` and `@cedar-policy/cedar-wasm`). When parsing JSON-formatted policy templates that define `is ... in` scope constraints on `principal` or `resource`, the parser used wildcard pattern matching that ignored the provided slot identifier (`?principal` or `?resource`). This omission allows malformed JSON templates to bypass initial deserialization validation, resulting in either a runtime panic during template linking or inverted slot bindings that alter authorization outcomes.

Amit Schendel
Amit Schendel
7 views•10 min read
•about 12 hours ago•GHSA-W9X9-J56R-H98R
8.2

GHSA-w9x9-j56r-h98r: Unauthenticated Confused Deputy Proxy in @eeacms/volto-chatbot

The @eeacms/volto-chatbot package for Plone Volto contains an unauthenticated confused deputy vulnerability (CWE-862) in versions 2.0.3 and earlier. The package registers Express middleware on wildcard proxy routes (`**/_v1_da/**` and `**/_v1_ha/**`) without verifying client authentication or restricting target endpoints. When processing incoming HTTP requests, the middleware automatically attaches server-side administrative credentials before forwarding the requests to upstream Danswer (Onyx) or Halloumi (LLMGW) API servers. This allows unauthenticated network attackers to execute arbitrary administrative API calls against the backend infrastructure.

Amit Schendel
Amit Schendel
7 views•8 min read