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



CVE-2026-125325

CVE-2026-125325: Cross-Request Information Disclosure via Shared BM25 Index Race Condition in pgEdge RAG Server

Amit Schendel
Amit Schendel
Senior Security Researcher

Nov 12, 2026·10 min read·1 visit

Executive Summary (TL;DR)

A concurrency flaw in pgEdge RAG Server (< 2.0.0) causes concurrent hybrid search requests to share and mutate a single in-memory BM25 keyword index. Attackers or concurrent users can receive document contents and synthesized LLM answers belonging to other tenants' filtered search scopes. Upgrading to version 2.0.0 resolves the issue by isolating BM25 index instances to individual requests.

pgEdge RAG Server versions prior to 2.0.0 contain a race condition in the hybrid search pipeline orchestrator (CWE-362, CWE-488). The server maintains a single shared, in-memory BM25 index instance across all incoming HTTP requests on a pipeline. Concurrent queries to the unauthenticated HTTP REST API interleave non-atomic clear, document addition, and search operations on this singleton index. As a result, documents fetched using one caller's search filter can be queried and returned to another concurrent caller, bypassing multi-tenant data isolation without generating any anomalous audit trail in server or database logs.

Vulnerability Overview

pgEdge RAG Server (pgedge-rag-server) is a Go-based application that exposes an HTTP REST API for Retrieval-Augmented Generation (RAG) workflows over PostgreSQL databases utilizing the pgvector extension. To improve retrieval relevance, the server implements hybrid search by combining dense vector similarity searches (executed within PostgreSQL) with sparse keyword scoring using an in-memory Okapi BM25 index.

In versions prior to 2.0.0, the pipeline orchestrator (internal/pipeline/orchestrator.go) instantiates a single *bm25.Index structure when the pipeline is initialized and stores its pointer on the shared Orchestrator struct. Because the HTTP API endpoints (/v1/pipelines/{name}) are unauthenticated by default and handle incoming requests concurrently across separate Go routines, all simultaneous queries targeting the same pipeline interact with this single in-memory index.

This architectural flaw introduces a race condition classified under CWE-362 (Concurrent Execution using Shared Resource with Improper Synchronization) and CWE-488 (Exposure of Data Element to Wrong Session). When multiple callers submit queries containing tenant-scoping or account-scoping filters, their execution threads interleave non-atomic state mutations on the shared BM25 index. Consequently, documents retrieved for one user or tenant filter are exposed to concurrent callers in both raw source snippets and LLM-synthesized responses.

Root Cause Analysis

The root cause of CVE-2026-125325 is a discrepancy between method-level synchronization and transaction-level atomicity during hybrid search execution. In internal/pipeline/orchestrator.go, the NewOrchestrator constructor allocated a single bm25.NewIndex() instance and assigned it to o.bm25Index. During every search invocation (func (o *Orchestrator) search), the orchestrator executed a three-step sequence on this shared field: clearing the index, populating it with filtered documents from PostgreSQL, and executing the keyword search.

While the underlying bm25.Index methods (Clear(), AddDocuments(), and Search()) used internal mutexes to prevent concurrent map read/write panics in the Go runtime, the three operations were not synchronized as a single atomic unit. Because each method released its lock immediately upon completion, concurrent goroutines executing search() freely interleaved between these calls.

Consider two concurrent requests: Request A (filtering for tenant = 'alpha') and Request B (filtering for tenant = 'beta'). Request A fetches Tenant Alpha's documents from PostgreSQL, calls o.bm25Index.Clear(), and calls o.bm25Index.AddDocuments(docs_alpha). Before Request A invokes o.bm25Index.Search(), the Go scheduler switches context or runs Request B in parallel on another OS thread. Request B calls o.bm25Index.Clear() and o.bm25Index.AddDocuments(docs_beta).

When Request A subsequently executes o.bm25Index.Search(), it evaluates its query terms against Tenant Beta's document corpus. Alternatively, if Request B executes Search() immediately after Request A finishes AddDocuments(docs_alpha), Request B extracts matches from Tenant Alpha's corpus. Because this state corruption occurs entirely within ephemeral application memory after the database query has completed, neither PostgreSQL query logs nor HTTP access logs record any indication that cross-tenant data leakage occurred.

Code Analysis

The vulnerability was remediated in Pull Request #44 via commit a09d4872356b510c81f11591845445c33ff45a84. Prior to the fix, Orchestrator held the bm25Index pointer as a struct member and mutated it in place during search():

// Vulnerable code prior to commit a09d4872356b510c81f11591845445c33ff45a84
type Orchestrator struct {
	completionProv Completer
	reranker       Reranker
	rerankTopK     int
	bm25Index      *bm25.Index // Shared across all requests on this pipeline
	tokenBudget    int
	topN           int
	logger         *slog.Logger
}
 
// Inside func (o *Orchestrator) search(...)
docs, err := o.dbPool.FetchDocuments(ctx, table, req.Filter)
if err != nil {
	o.logger.Warn("failed to fetch documents for BM25", "table", table.Table, "error", err)
	continue
}
 
// Non-atomic sequence on shared state
o.bm25Index.Clear()
o.bm25Index.AddDocuments(docs)
bm25Results := o.bm25Index.Search(req.Query, topN*2)

The patch removes bm25Index from the Orchestrator struct entirely and instantiates a local, request-scoped bm25.NewIndex() inside the search loop. Additionally, the commit bounds the number of documents fetched from PostgreSQL by introducing maxBM25Docs (bm25_max_documents, defaulting to 10,000):

--- a/internal/pipeline/orchestrator.go
+++ b/internal/pipeline/orchestrator.go
@@ -33,7 +33,6 @@ type Orchestrator struct {
 	completionProv Completer
 	reranker       Reranker
 	rerankTopK     int
-	bm25Index      *bm25.Index
 	tokenBudget    int
 	topN           int
 	logger         *slog.Logger
@@ -66,7 +65,6 @@ func NewOrchestrator(cfg OrchestratorConfig) *Orchestrator {
 		completionProv: cfg.CompletionProv,
 		reranker:       cfg.Reranker,
 		rerankTopK:     cfg.RerankTopK,
-		bm25Index:      bm25.NewIndex(),
 		tokenBudget:    cfg.TokenBudget,
 		topN:           cfg.TopN,
 		logger:         logger,
@@ -345,7 +348,7 @@ func (o *Orchestrator) search(
 			continue
 		}
 
-		docs, err := o.dbPool.FetchDocuments(ctx, table, req.Filter)
+		docs, err := o.dbPool.FetchDocuments(ctx, table, req.Filter, maxBM25Docs)
 		if err != nil {
 			o.logger.Warn("failed to fetch documents for BM25",
 				"table", table.Table, "error", err)
@@ -354,9 +357,28 @@ func (o *Orchestrator) search(
 			continue
 		}
 
-		o.bm25Index.Clear()
-		o.bm25Index.AddDocuments(docs)
-		bm25Results := o.bm25Index.Search(req.Query, topN*2)
+		if len(docs) >= maxBM25Docs {
+			o.logger.Warn(
+				"keyword search corpus truncated by bm25_max_documents; "+
+					"keyword coverage for this request is partial",
+				"table", table.Table, "limit", maxBM25Docs,
+			)
+		}
+
+		// Ephemeral index scoped strictly to the current request and table
+		index := bm25.NewIndex()
+		index.AddDocuments(docs)
+		bm25Results := index.Search(req.Query, topN*2)

The fix is complete and structurally eliminates the race condition. Rather than wrapping the three steps in a pipeline-wide mutex—which would serialize all hybrid search requests and degrade throughput under load—allocating bm25.NewIndex() on the local call stack guarantees memory isolation between goroutines. Once search() completes, the ephemeral index goes out of scope and is reclaimed by the Go garbage collector.

Exploitation

Exploiting CVE-2026-125325 requires network access to the pgEdge RAG Server HTTP interface (listening on TCP port 8080 by default in v1.x) and a target pipeline with hybrid search enabled (hybrid_enabled: true, which is the default setting). No authentication credentials or special privileges are required, nor does the attacker need to craft malformed JSON or SQL injection payloads.

In a multi-tenant deployment where an upstream application passes tenant identifiers in the filter field to isolate customer documents, an attacker can exploit the race window by sending a continuous stream of concurrent POST requests to /v1/pipelines/{pipeline_name}:

POST /v1/pipelines/enterprise-knowledge-base HTTP/1.1
Host: rag-server.internal:8080
Content-Type: application/json
 
{
  "query": "agreement revenue acquisition confidential terms",
  "include_sources": true,
  "top_n": 10,
  "filter": {
    "conditions": [
      {
        "column": "tenant_id",
        "operator": "=",
        "value": "unprivileged-tenant"
      }
    ]
  }
}

When a legitimate user or service queries the same pipeline concurrently with `

Concurrency Regression & Proof of Concept Analysis

While no standalone malicious exploit kit has been published in the wild, commit a09d4872356b510c81f11591845445c33ff45a84 includes an automated regression test in internal/pipeline/orchestrator_test.go (TestSearch_ConcurrentRequestsDoNotShareKeywordIndex) that functions as a deterministic proof of concept against unpatched code.

func TestSearch_ConcurrentRequestsDoNotShareKeywordIndex(t *testing.T) {
	corpus := func(tenant string) map[string]string {
		docs := make(map[string]string, 32)
		for i := 0; i < 32; i++ {
			id := tenant + "-" + string(rune('a'+i%26))
			docs[id] = "widget documentation for tenant " + tenant
		}
		return docs
	}
 
	backend := &MockSearchBackend{
		FetchDocumentsFunc: func(
			ctx context.Context, table config.TableSource,
			filter *config.Filter, maxDocuments int,
		) (map[string]string, error) {
			tenant, _ := filter.Conditions[0].Value.(string)
			return corpus(tenant), nil
		},
	}
 
	orch := NewOrchestrator(OrchestratorConfig{
		Pipeline:    hybridPipeline(nil),
		DBPool:      backend,
		TokenBudget: DefaultTokenBudget,
		TopN:        DefaultTopN,
	})
 
	const iterations = 40
	var wg sync.WaitGroup
	errCh := make(chan error, iterations*2)
 
	for _, tenant := range []string{"alpha", "beta"} {
		for i := 0; i < iterations; i++ {
			wg.Add(1)
			go func(tenant string) {
				defer wg.Done()
				req := QueryRequest{
					Query: "widget",
					Filter: &config.Filter{
						Conditions: []config.FilterCondition{
							{Column: "tenant", Operator: "=", Value: tenant},
						},
					},
				}
				results, err := orch.search(context.Background(), req, []float32{0.1}, 5)
				if err != nil {
					errCh <- err
					return
				}
				for _, r := range results {
					if !strings.HasPrefix(r.ID, tenant+"-") {
						errCh <- errors.New("tenant " + tenant + " received a result belonging to another request's filter: " + r.ID)
						return
					}
				}
			}(tenant)
		}
	}
	wg.Wait()
	close(errCh)
	for err := range errCh {
		t.Error(err)
	}
}

The test spawns 80 concurrent goroutines (40 for tenant alpha and 40 for tenant beta), each querying for the keyword "widget" with its respective tenant filter. When run against pgEdge RAG Server versions prior to 2.0.0, goroutines for alpha routinely receive document IDs prefixed with beta- (and vice versa). When `

Impact Assessment

CVE-2026-125325 is rated Medium severity with a CVSS v3.1 base score of 5.9 (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N). The Attack Complexity is evaluated as High (AC:H) because successful data harvesting requires concurrent execution overlap with legitimate victim queries targeting the same pipeline. However, on busy multi-tenant RAG pipelines, this concurrency occurs naturally during normal production operations without active attacker timing orchestration.

The primary impact is a high-severity loss of Confidentiality (C:H). If the pipeline configuration permits returning raw source documents (allow_include_sources: true) and the caller passes "include_sources": true in the request body, the full text of documents belonging to another tenant's filter is returned directly in the HTTP response's sources array. Even if raw sources are disabled, the leaked documents are injected into the LLM prompt context, causing the completion provider to synthesize answers containing sensitive information from the victim tenant's corpus.

> [!NOTE] > In addition to the confidentiality impact, versions prior to 2.0.0 lacked a LIMIT clause in FetchDocuments, causing every hybrid query to load all rows matching the filter from PostgreSQL into memory before building the BM25 index. Large tables could experience high memory consumption and latency spikes alongside the widened race window.

From an incident response perspective, this vulnerability presents a forensic blind spot. Because the database query (FetchDocuments) executes with the caller's legitimate filter and the cross-contamination happens strictly within Go memory pointers during o.bm25Index manipulation, neither PostgreSQL audit logs nor HTTP access logs indicate which requests received cross-tenant data.

Remediation & Mitigation

Administrators and developers using pgEdge RAG Server must upgrade to version 2.0.0 or later (v2.0.0-beta1 and subsequent releases contain commit a09d4872356b510c81f11591845445c33ff45a84). When upgrading via native Linux packages (RPM or Debian), note that version 2.0.0 introduces a new package name (pgedge-rag-server2) and default port (8081) to allow side-by-side migration. Operators must explicitly stop and disable the legacy v1.x pgedge-rag-server systemd unit (/usr/bin/pgedge-rag-server on port 8080) to ensure clients do not continue querying the vulnerable binary.

If an immediate upgrade to version 2.0.0 is not possible, operators can completely bypass the vulnerable code path by disabling BM25 keyword search in the pipeline configuration (/etc/pgedge/pgedge-rag-server.yaml). Setting hybrid_enabled: false or configuring vector_weight: 1.0 forces the server to rely exclusively on PostgreSQL pgvector dense similarity searches:

pipelines:
  - name: "production-pipeline"
    search:
      hybrid_enabled: false
      vector_weight: 1.0
    allow_include_sources: false

On already-patched 2.0.0+ deployments where hybrid search is not required for specific requests, callers can also pass "disable_hybrid": true in the JSON query payload or tune search.bm25_max_documents (default 10000) to cap memory usage. Finally, because pgEdge RAG Server does not enforce authentication natively, production deployments should be placed behind an authenticating reverse proxy or API gateway that validates tenant claims and enforces per-client rate limits.

Official Patches

pgEdgeOfficial GitHub Security Advisory GHSA-w437-9mwm-j6vq for CVE-2026-125325
pgEdgepgEdge RAG Server v2.0.0 Release Notes
pgEdgePull Request #44 implementing per-request BM25 index isolation and document fetch limits

Fix Analysis (1)

Technical Appendix

CVSS Score
5.9/ 10
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N
EPSS Probability
0.27%
Top 80% most exploited

Affected Systems

pgEdge RAG Server (pgedge-rag-server)

Affected Versions Detail

Product
Affected Versions
Fixed Version
pgEdge RAG Server
pgEdge
< 2.0.0 (including 1.0.0, 1.0.0-beta1 through 1.0.0-beta3-test, and 1.0.0-alpha1 through 1.0.0-alpha5)2.0.0
AttributeDetail
CVE IDCVE-2026-125325 (GHSA-w437-9mwm-j6vq)
Vulnerability ClassCWE-362 (Race Condition) / CWE-488 (Exposure of Data Element to Wrong Session)
CVSS v3.1 Score5.9 Medium (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N)
Attack VectorNetwork (Unauthenticated HTTP POST to /v1/pipelines/{name})
Primary ImpactCross-request / Cross-tenant information disclosure of filtered RAG documents
Affected VersionspgEdge RAG Server < 2.0.0
Patched VersionpgEdge RAG Server 2.0.0
EPSS / KEV Status0.27% (19.59th percentile) / Not Listed in CISA KEV

MITRE ATT&CK Mapping

T1068Exploitation for Privilege Escalation
Privilege Escalation
CWE-362
Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')

The program contains a code sequence that can run concurrently with other code, and the code sequence requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence that is operating concurrently.

Known Exploits & Detection

GitHubOfficial concurrency regression test (TestSearch_ConcurrentRequestsDoNotShareKeywordIndex) demonstrating cross-tenant BM25 index leakage under concurrent load.

Vulnerability Timeline

Fix committed and merged in Pull Request #44 (commit a09d4872356b510c81f11591845445c33ff45a84).
2026-07-29
pgEdge RAG Server v2.0.0-beta1 pre-release tagged containing the fix.
2026-08-17
pgEdge RAG Server v2.0.0 officially released.
2026-08-25
Changelog updated linking security advisory GHSA-w437-9mwm-j6vq.
2026-09-03
CVE-2026-125325 published by GitHub CNA and NVD.
2026-11-10
EPSS score recorded at 0.00271 (19.59th percentile).
2026-11-12

References & Sources

  • [1]GitHub Security Advisory GHSA-w437-9mwm-j6vq
  • [2]pgEdge RAG Server Pull Request #44
  • [3]Patch Commit a09d4872356b510c81f11591845445c33ff45a84
  • [4]pgEdge RAG Server Release v2.0.0
  • [5]NVD - CVE-2026-125325
  • [6]CVE.org Record - CVE-2026-125325
  • [7]CVE List V5 JSON Record - CVE-2026-125325
  • [8]pgEdge Security Documentation

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

•about 3 hours ago•CVE-2026-118740
9.1

CVE-2026-118740: Missing Authorization in Microsoft Aspire Dashboard Blazor Router

CVE-2026-118740 is a Missing Authorization (CWE-862) vulnerability in the Microsoft Aspire Dashboard and Aspire.Dashboard.Sdk.linux-x64 packages. In versions 13.6.1 prior to 13.6.5, an architectural flaw in the Blazor routing configuration (Routes.razor) causes unmapped (404 / Not-Found) routes to bypass the AuthorizeRouteView component. By navigating to an arbitrary non-existent route, an unauthenticated remote attacker forces the application into the NotFound template, which unconditionally renders the primary MainLayout component and initializes global user interface services, interaction providers, and underlying SignalR/Blazor circuits without verifying authentication credentials.

Alon Barad
Alon Barad
4 views•9 min read
•1 day ago•CVE-2026-120006
7.5

CVE-2026-120006: Elevation of Privilege in .NET Aspire via Unintended Dashboard Endpoint Tunneling

An elevation of privilege vulnerability exists in .NET Aspire versions prior to 13.6.5 due to the insertion of sensitive information into sent data (CWE-201). When the Developer Control Plane (DCP) container tunneling feature is enabled, the orchestration engine implicitly exposes all endpoints of the Aspire Dashboard—including its administrative frontend and management APIs—to containerized workloads instead of restricting tunnel access solely to OpenTelemetry Protocol (OTLP) collection endpoints. This exposure allows an attacker operating within a compromised or untrusted container to query the dashboard frontend, extract sensitive environment variables and connection strings, and elevate privileges across the host orchestration boundary.

Amit Schendel
Amit Schendel
5 views•10 min read
•1 day ago•CVE-2026-118750
7.5

CVE-2026-118750: Uncontrolled Resource Allocation in ASP.NET Core FileBufferingReadStream

CVE-2026-118750 is an uncontrolled resource allocation vulnerability (CWE-770) affecting the Microsoft.AspNetCore.WebUtilities component in ASP.NET Core. Specifically, the FileBufferingReadStream class eagerly allocates large memory buffers from the shared ArrayPool<byte> inside its constructor prior to reading any incoming request payload data. An unauthenticated remote attacker can exploit this behavior by opening numerous concurrent connections and stalling or omitting request body transmission, exhausting shared memory pools and causing a server-wide Denial of Service (DoS).

Amit Schendel
Amit Schendel
6 views•10 min read
•1 day ago•CVE-2026-114155
7.5

CVE-2026-114155: Local Elevation of Privilege via TOCTOU Race Condition in Microsoft .NET dotnet-watch

CVE-2026-114155 is a local elevation of privilege vulnerability affecting Microsoft .NET on Linux and macOS platforms. The vulnerability exists within the dotnet-watch (Hot Reload) component and is caused by a Time-of-Check Time-of-Use (TOCTOU) race condition (CWE-367) during the generation and execution of temporary XML solution files (.slnx) in shared system temporary directories. A local attacker with low privileges can intercept and modify the temporary solution file before MSBuild evaluates it, achieving arbitrary code execution in the security context of the user running dotnet-watch.

Alon Barad
Alon Barad
6 views•9 min read
•1 day ago•CVE-2026-118748
7.5

CVE-2026-118748: Unbounded Memory Allocation Denial of Service in ASP.NET Core Blazor Static-SSR

CVE-2026-118748 is a remote Denial of Service (DoS) vulnerability in Microsoft ASP.NET Core (.NET 11.0 preview releases prior to 11.0.0) and Microsoft Visual Studio 2026 (prior to 18.12.0). The flaw originates from unbounded resource allocation (CWE-770) within the Blazor static server-side rendering (static-SSR) validation pipeline, where dynamic or attacker-supplied form field identifiers are cached in a singleton-scoped memory structure without size limits or eviction policies. Unauthenticated remote attackers can exploit this condition over the network by sending repeated HTTP requests with unique field names, exhausting heap memory and terminating the host process via an unhandled OutOfMemoryException.

Alon Barad
Alon Barad
11 views•9 min read
•1 day ago•CVE-2026-114150
5.9

CVE-2026-114150: Remote Information Disclosure via Uninitialized Pooled Memory in ASP.NET Core Response Buffering

CVE-2026-114150 is a network-accessible information disclosure vulnerability affecting Microsoft ASP.NET Core in .NET 11.0 preview releases and Microsoft Visual Studio 2026 prior to version 18.12.0. The flaw stems from the use of uninitialized resources (CWE-908) within response buffering components, specifically ResponseBufferingStream used by middleware such as HTTP Logging and Output Caching. Because rented memory segments from ArrayPool<byte> or MemoryPool<byte> are not cleared upon return or strictly bounded to the exact written byte count during subsequent reads, residual response payloads from earlier HTTP requests remain in the buffer tail. Downstream consumers or log sinks reading these buffers expose confidential cross-session data, including authentication tokens and personally identifiable information.

Amit Schendel
Amit Schendel
4 views•11 min read