Safeguard
Application Security•

LangGraph SDK CVE-2026-104873: Auth Handlers Ignored actions=

In langgraph-sdk up to 0.4.3, @auth.on.threads(actions=[...]) silently applied to every action, so a create-only handler could approve reads, updates and deletes.

Safeguard Security Team
5 min read

On 5 October 2026, LangChain published GHSA-fvww-7h3r-vfhp (CVE-2026-104873) for langgraph-sdk, rated high. The custom authorization decorators for threads, assistants and crons accepted an actions= argument and then ignored it. A handler meant for one action was registered for every action on that resource.

Because a resource-wide handler is chosen before broader fallback handlers, any authorization check living in a fallback may never have run. An authenticated user could then read, update or delete another user's thread, if the handler that matched didn't check ownership itself.

What's affected

  • Package: langgraph-sdk on PyPI (Python only).
  • Affected: 0.1.45 through 0.4.3.
  • Fixed: 0.4.4, which PyPI shows was uploaded on 27 August 2026. The current release is 0.4.8.
  • Decorators: @auth.on.threads, @auth.on.assistants and @auth.on.crons, when called with actions=.

The fix commit shows how plain the bug was. In the affected code, the resource-specific decorator registered every handler under the action "*", and the resources and actions arguments were assigned to a throwaway variable with the comment "Accept keyword-only parameters for future filtering behavior; referenced to satisfy linters." The patched decorator registers the handler only for the listed actions, and rejects empty, duplicate or invalid action lists.

The advisory's example:

@auth.on.threads(actions=["create"])
async def allow_thread_create(ctx, value):
    return None

The developer intended this to cover thread creation only. On affected versions it matched thread reads, updates, deletes and searches too. The SDK's own docstring says a handler returning None or True accepts the request, so it allowed them, ahead of any stricter global handler.

How to check if you're exposed

The advisory is specific: only Python deployments that use actions= on the resource-scoped decorators are affected. If you never pass actions= there, the bug doesn't apply.

Find the version and the pattern:

pip show langgraph-sdk | grep -i ^version
grep -rnE '@auth\.on\.(threads|assistants|crons)(\.[a-z_]+)?\(\s*actions\s*=' --include=*.py .

For each match, ask two questions:

  1. Does this handler check ownership or permissions for every request it receives, regardless of action? The advisory says deployments stay protected if the matched handler independently enforces the needed action, ownership or permission checks.
  2. Do you have a broader handler, such as a global @auth.on, that you were counting on to enforce ownership for the other actions? If so, that handler was skipped for this resource on affected versions.

A handler that returns None or a permissive filter for a create action, combined with an ownership check that lives in a fallback, is the vulnerable shape. The test in the fix commit shows the intended layout: a global deny handler plus action-scoped handlers that open specific actions.

What to do now

  1. Upgrade langgraph-sdk to 0.4.4 or later in the image that runs your LangGraph server. Pin it in the server's requirements, not just in client code.
  2. Add a global default-deny handler. The 0.4.4 changelog says unmatched custom-auth paths remain allowed, and recommends that deployments using action-scoped handlers configure one.
  3. If you run langgraph-api 0.10 or later, read its startup warnings. The changelog notes it warns about uncovered paths.
  4. Make every handler enforce ownership on its own. Return a filter on the owner field rather than None wherever the resource belongs to a user.
  5. Review thread, assistant and cron access logs for the exposure window. Look for users reading or deleting resources whose metadata names a different owner.

Why it matters

Authorization frameworks fail quietly. An authentication bug usually breaks something visible, but an authorization rule that's broader than intended just lets requests through, and nobody files a bug about access that worked. This one was hidden behind an argument the API accepted and documented intent for, while the implementation discarded it.

Two habits catch this class of issue. Write negative tests for authorization: a user who should be denied, asserting a 403. And default to deny, so a rule that matches less than you expected fails closed instead of open. The LangGraph fix pushes both, by validating action lists and steering users toward a global deny handler.

Try it yourself

Write one test against your LangGraph server: create a thread as user A, then try to read and delete it as user B. If both calls succeed on your current version, you've found the problem without reading any code. Static analysis can also flag decorator patterns like this before deploy:

Click through it at your own pace, or open it full screen.

Quick check

Am I affected if I only use @auth.on.threads without actions=?

No. The advisory says only Python deployments that pass actions= to the resource-scoped decorators are affected. Without actions=, the handler was meant to cover every action anyway.

Is there a workaround short of upgrading?

The advisory lists none. Making every matched handler enforce ownership itself removes the impact, but upgrading to 0.4.4 is the fix.

Does the fix change what happens to requests no handler covers?

No. The changelog says unmatched paths remain allowed, which is why it recommends adding a global default-deny handler.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.