OpenBMB / OpenBMB/UltraRAG

Bug: CitationRegistry global state causes cross-request citation contamination

Open
#394 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
5.7k
Forks
449
Avg merge
2d 17h
Merged PRs (30d)
13

Description

Bug Description

CitationRegistry stores its state in a class-level dictionary (_instances) shared across all requests. init_citation_registry() calls CitationRegistry.reset(), which wipes this global dict for every concurrent session. When two requests run the init_citation_registryassign_citation_ids_stateful pipeline at the same time, one request's reset destroys the other's in-progress state, producing corrupted or swapped citation IDs in responses.

Location

servers/custom/src/custom.py, ~line 405:

class CitationRegistry:
    _instances: Dict[int, Dict[str, Any]] = {}  # class-level, shared across all requests

    @classmethod
    def reset(cls):
        cls._instances = {}                      # wipes state for ALL concurrent sessions

~line 435:

@app.tool(output="q_ls->q_ls")
def init_citation_registry(q_ls: List[str]) -> Dict[str, Any]:
    CitationRegistry.reset()                     # global reset triggered per request
    return {"q_ls": q_ls}

Reproduction

  1. Send two concurrent requests that both invoke init_citation_registry followed by assign_citation_ids_stateful.
  2. Request A calls reset() while Request B is mid-way through assign_citation_ids_stateful.
  3. Request B's accumulated citations are wiped; it returns citation IDs starting from 1 for documents it had already assigned higher IDs.

Impact

Users receive incorrect citation numbers in answers, causing documents to be cited under wrong IDs. In multi-tenant deployments this also constitutes a cross-session information leak (one user's citation state can be reset by another user's request).

Suggested Fix

Scope registry state per request using a unique session/request ID rather than global class state:

def init_citation_registry(q_ls: List[str], request_id: str) -> Dict[str, Any]:
    CitationRegistry._instances[request_id] = {}
    return {"q_ls": q_ls, "request_id": request_id}

Or pass a fresh CitationRegistry instance through the pipeline context instead of using class-level storage.


Found via automated codebase analysis. Happy to submit a PR if this is confirmed.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in servers/custom/src/custom.py around CitationRegistry, init_citation_registry, and assign_citation_ids_stateful. Trace how registry state moves through the concurrent request pipeline and compare the proposed request-scoped or instance-based approaches. Done means concurrent requests retain isolated citation state and return correct, non-swapped citation IDs without cross-session resets.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api, backend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.