Skip to content

FastGPT: workflow runtime can execute another user's private HTTP toolset

Moderate
c121914yu published GHSA-93r3-wqq3-c5ch Jul 1, 2026

Package

fastgpt (labring)

Affected versions

>= 4.14.17, <= 4.14.23 = 4.15.0-beta3

Patched versions

4.15.0-beta5

Description

Summary

An authenticated FastGPT user can save a workflow node that points to another user's private HTTP toolset by using a crafted saved tool id such as:

http-<victim_toolset_app_id>/<tool_name>

The normal toolset routes correctly deny access to that toolset. For example, the attacker cannot preview it, read its detail, list its versions, update it, or obtain read/write/manage permission. The problem is that the workflow save/runtime path does not apply the same authorization check to the referenced toolset. After the crafted workflow is saved, /api/v2/chat/completions resolves the saved reference and executes the victim-owned HTTP tool.

I reproduced this on:

  • v4.14.17
  • v4.14.22
  • v4.14.23
  • v4.15.0-beta3

I am reporting this as CWE-863: Incorrect Authorization. It is not a direct SSRF report and it is not an arbitrary code execution report.

Impact

The attacker can cause FastGPT to execute a private HTTP toolset that belongs to another user/team. In the basic PoC, the attacker receives the response of a victim-owned HTTP tool. In an additional local-only impact test, the victim toolset carried a synthetic credential for a mock external SaaS endpoint. The attacker still had no official toolset permission, but the saved workflow runtime used the victim toolset's credentialed capability to read a tenant-private marker and update external mock state.

The external-service test used only a local mock service and a synthetic credential. No real third-party service, cloud metadata endpoint, intranet host, production system, or real secret was used.

Security Boundary

The crossed boundary is the ownership and authorization boundary between workflow apps and private HTTP toolsets.

In the PoC:

  • user_a owns a private HTTP toolset.
  • user_b owns a separate workflow app.
  • user_b has no permission on user_a's toolset.
  • Official toolset routes enforce that denial.
  • Workflow runtime still executes user_a's toolset when user_b saves a crafted reference in user_b's own workflow.

This affects another user, not only the reporter's own account.

Preconditions

  • The attacker is authenticated.
  • The attacker can create/save/run their own workflow app.
  • A victim has a private HTTP toolset.
  • The attacker knows a valid combined tool id in the form http-<toolset_app_id>/<tool_name>.

I am not claiming low attack complexity for identifier discovery yet. I tested a possible product collaboration path, but my local OSS build did not expose the formal product collaboration API, so I am keeping AC:H.

Environment Used

Main local reproduction:

  • FastGPT bound to http://127.0.0.1:3300
  • local mock HTTP/OpenAPI service reachable from FastGPT containers
  • two ordinary users with separate team/tmb ownership
  • no shared permission from the victim toolset to the attacker workflow app

Concrete object values from one v4.14.22 run:

user_a team/tmb: 6a213feed15b4872fc8de667 / 6a213feed15b4872fc8de668
user_b team/tmb: 6a213feed15b4872fc8de66c / 6a213feed15b4872fc8de66d
user_a toolset id: 6a2141e4b81749a85819860e
tool name: sandbox_echo
combined tool id: http-6a2141e4b81749a85819860e/sandbox_echo
user_b workflow app id: 6a2141e4b81749a858198632

These ids are not required to reproduce the issue; they are included so the maintainer can see the exact relationship tested.

Reproduction Steps

1. Create the actors

Create two normal users:

user_a: victim toolset owner
user_b: attacker workflow owner

They should not share the target app/toolset permission.

2. Create a private HTTP toolset as user_a

Create an HTTP toolset owned by user_a with a tool named sandbox_echo. Point it to any harmless endpoint controlled by the tester. My local endpoint returned:

{
  "ok": true,
  "echo": "sandbox",
  "mock": "fastgpt-sandbox"
}

3. Create a workflow app as user_b

Create a minimal workflow app owned by user_b.

4. Confirm user_b cannot access user_a's toolset through official routes

Using user_b's session, verify that the normal routes deny access to user_a's toolset:

GET /api/core/app/tool/getPreviewNode?appId=http-<user_a_toolset_id>/sandbox_echo
POST /api/core/app/tool/getVersionList
GET /api/core/app/detail?appId=<user_a_toolset_id>
POST /api/core/app/update?appId=<user_a_toolset_id>
GET /api/core/app/getPermission?appId=<user_a_toolset_id>

Observed result from my run:

getPreviewNode as user_b: denied with statusText=unAuthApp, code=502001
getVersionList as user_b: denied with statusText=unAuthApp, code=502001
detail as user_b: denied with statusText=unAuthApp, code=502001
update as user_b: denied with statusText=unAuthApp, code=502001
getPermission as user_b: HTTP 200, but hasReadPer=false, hasWritePer=false, hasManagePer=false, isOwner=false

As a positive control, the same preview/detail/version/permission checks succeeded as user_a.

5. Save a crafted workflow node as user_b

Save or publish user_b's workflow app with an HTTP tool node whose saved configuration contains:

toolConfig.httpTool.toolId = http-<user_a_toolset_id>/sandbox_echo

The minimal graph used in my run was:

workflowStartNodeId -> p0aCrossHttpTool -> p0aAnswer

The save request returned HTTP 200 / JSON code 200 even though the referenced toolset belonged to user_a.

6. Confirm the crafted reference was persisted

After saving, the workflow app owned by user_b contained:

containsCrossOwnedToolId=true
crossOwnedToolIdMatches=["http-6a2141e4b81749a85819860e/sandbox_echo"]

The latest published workflow version also contained the same cross-owned id. Ownership was different:

user_a toolset team/tmb: 6a213feed15b4872fc8de667 / 6a213feed15b4872fc8de668
user_b app team/tmb:    6a213feed15b4872fc8de66c / 6a213feed15b4872fc8de66d

7. Trigger workflow runtime as user_b

Call the chat runtime against user_b's workflow app:

POST /api/v2/chat/completions

Observed result:

HTTP status: 200
executed node: p0aCrossHttpTool
executed node name: Cross-owned HTTP sandbox_echo
tool result: {"ok":true,"echo":"sandbox","mock":"fastgpt-sandbox"}

8. Re-run the official negative controls

After runtime execution, user_b was still denied by the official toolset access routes. This confirms that the runtime path executed a toolset that the normal authorization model still considered inaccessible to user_b.

Version Matrix

I repeated the same test flow across current and historical local sandboxes:

Version Baseline Negative controls Owner positive control Crafted save Runtime execution Result
v4.14.17 pass pass pass pass pass affected
v4.14.22 pass pass pass pass pass affected
v4.14.23 pass pass pass pass pass affected
v4.15.0-beta3 pass pass pass pass pass affected

For v4.14.23 and v4.15.0-beta3, the summary values were:

db_contains_cross_owned=true
ownership_different=true
runtime_status_code=200
runtime_contains_mock=true
t1_baseline_pass=true
t2_negative_pass=true
t3_positive_pass=true
t4_crafted_save_pass=true
t5_runtime_pass=true
t6_separation_pass=true

Additional Impact Test: Credentialed External Mock

This section is optional impact context. It is not needed for the root authorization bug.

I configured user_a's private HTTP toolset with a synthetic header credential and pointed it at a local mock external SaaS service:

POST /tenant-a/private-record
POST /tenant-a/update-state
GET  /tenant-a/state

The mock rejected direct requests without the expected header. It did not log or return the secret value.

Observed on both v4.14.23 and v4.15.0-beta3:

direct no-credential access: denied
official toolset access by user_b: denied
saved crafted references by user_b: succeeded
runtime private record read: saw_expected_header=true
external private_record_reads: 0 -> 1
runtime update: saw_expected_header=true
external update_count: 0 -> 1
secret value written to evidence: false

If the project treats that local mock as a separate authorization scope, the same root bug has a stronger cross-scope impact. If not, the conservative in-product impact still stands.

Root Cause

The likely issue is that the save/update/publish path authorizes only the workflow app being edited. It does not validate every referenced toolConfig.httpTool.toolId against the editor's permission on the referenced parent toolset.

At runtime, FastGPT trusts the persisted combined tool id, loads the referenced parent toolset version, and executes the saved HTTP tool configuration. That runtime resolution does not appear to re-authorize the referenced toolset for the app/actor context that is executing the workflow.

The normal preview/detail/version/permission routes do perform authorization checks, which is why they fail closed for user_b. The missing check is specifically in the saved workflow reference and runtime path.

The PoC does not rely on any direct URL/schema/tool-run primitive. I did not use:

POST /api/core/app/httpTools/runTool
/api/core/app/httpTools/getApiSchemaByUrl
/api/core/app/mcpTools/getTools
/api/core/app/mcpTools/runTool
cloud metadata targets
real intranet hosts
third-party systems

The demonstrated issue is that a saved workflow can reference and execute a private toolset object that the attacker cannot access through official routes.

CVSS

Conservative score:

CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:H/A:N = 5.9 Medium

Rationale:

  • AV:N: the behavior is reachable through FastGPT HTTP APIs.
  • AC:H: this PoC assumes the attacker knows the target combined tool id and tool name.
  • PR:L: the attacker needs a normal authenticated account.
  • UI:N: the victim does not need to interact after the attacker has the target reference.
  • S:U: conservative framing treats the impact as inside FastGPT's authorization boundary.
  • C:L: the attacker can receive output from the invoked private HTTP tool.
  • I:H: the attacker can trigger another user's private tool capability.
  • A:N: no availability impact was demonstrated.

Conditional score if the credentialed external mock is treated as a changed scope:

CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:H/A:N = 7.1 High

Suggested Fix

I suggest fixing this in both places:

  1. Save/update/publish validation:

Reject workflow nodes whose HTTP/MCP combined tool ids reference a parent app/toolset the editor cannot read or use.

  1. Runtime validation:

Before resolving http-<parentAppId>/<toolName> or mcp-<parentAppId>/<toolName>, re-authorize the referenced parent toolset against the current execution context or a durable saved grant.

  1. Data model hardening:

Prefer storing normalized, grant-backed tool references rather than trusting raw combined ids submitted in workflow JSON.

Regression test suggestion:

user_a owns a private HTTP toolset
user_b owns a workflow app
user_b cannot preview/detail/version/read user_a's toolset
user_b saves a node with http-<user_a_toolset_id>/<tool_name>
save is rejected, or runtime refuses execution
user_a's own workflow/toolset use still works
legitimately shared toolsets still work

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
High
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:H/A:N

CVE ID

CVE-2026-61643

Weaknesses

Incorrect Authorization

The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. Learn more on MITRE.

Credits