GHSA-r553-q33m-v7pf: Appsmith Git widget name path traversal
I reported a Git widget serialization path traversal in Appsmith that was published as GHSA-r553-q33m-v7pf.
The issue affected Git-connected Appsmith applications. An authenticated user with edit access to an application could place traversal sequences in a widget name. When Appsmith later serialized page widgets for Git status or commit operations, that name was used as part of a server-side filesystem path and could escape the application’s Git repository.
Advisory
- CVE: No known CVE
- GitHub Advisory:
GHSA-r553-q33m-v7pf - Package:
appsmithorg/appsmith - Affected versions:
<= v2.0 - Patched version:
v2.1 - Severity: High 7.1/10
- CVSS:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L - CWE:
CWE-22: Improper Limitation of a Pathname to a Restricted Directory
Preconditions
The vulnerable deployment shape required all of the following:
- the attacker is authenticated;
- the attacker can edit the target Appsmith application layout;
- the target application is connected to Git, or another Git serialization path such as status, commit, or auto-commit is triggered.
This was not an unauthenticated bug. The interesting boundary is between an Appsmith application editor and the server filesystem used by the Appsmith instance.
Root Cause
Appsmith serializes application pages, actions, and widgets into files when Git integration needs to compute status or create commits.
The vulnerable path was the widget serializer. The layout update API accepted arbitrary widgetName values in the page DSL:
1
PUT /api/v1/layouts/{layoutId}/pages/{pageId}
During Git serialization, Appsmith derived a widget file path from that widget name. The vulnerable flow was:
FileUtilsCEImpl.saveApplicationToGitRepo()extracted the widget name from the DSL key.DSLTransformerHelper.getPathToWidgetFile()derived a child path for the widget file.FileOperationsCEv2Impl.saveWidgets()created directories and wroteresourceName + ".json".- The final resolved widget path was not validated with the same Git-root boundary check used by other Git file write helpers.
The result was a path traversal in the server-side Git file serialization path. A widget name such as:
1
../../../../../../../../tmp/appsmith-widget-traversal
could cause the Git status serialization path to create a file like:
1
/tmp/appsmith-widget-traversal.json
inside the Appsmith container.
Impact
The impact is a server-side arbitrary .json file write, constrained by the Appsmith process user’s filesystem permissions and by the forced .json extension.
Practical impact includes:
- creating or overwriting attacker-chosen
.jsonfiles where the parent directory exists and is writable; - escaping the intended Git repository boundary for one application;
- corrupting Appsmith application/server assets or JSON-like configuration files reachable by the process user;
- crossing an important privilege boundary in self-hosted or multi-user deployments where application editors are not expected to write server filesystem content.
I did not prove standalone remote code execution from this primitive. The primitive writes JSON content and appends .json to the attacker-controlled path. It should be treated as arbitrary file write/path traversal unless a separate JSON reload or execution sink is identified.
Why This Was Easy To Miss
This looked similar to an older Appsmith Git path traversal class, but the sink was different.
The earlier issue involved ActionCollection name serialization. This one was in the widget serialization path: DSLTransformerHelper.getPathToWidgetFile() plus FileOperationsCEv2Impl.saveWidgets().
That distinction matters because a fix for one serializer does not automatically protect every serializer that writes application data into a Git repository. Each path-building helper has to preserve the same invariant: user-controlled names may influence logical resource names, but they must not become raw filesystem path components outside the repository root.
The bug also hides behind a secondary trigger. The malicious widget name is accepted during layout editing, but the filesystem write appears when Git status, commit, or another serialization path runs. That split between data persistence and file generation makes the issue quieter during normal UI testing.
Fix Direction
The fix needs to validate the normalized final widget file path, not only nearby parent paths or other serializer paths.
Defensive principles:
- reject widget names containing
/,\,.., NUL bytes, or other filesystem separators before persistence or refactor operations; - call the Git-root boundary validation on the final
path.resolve(resourceName + ".json")value inside the widget save path; - avoid using user-controlled widget names as raw path components;
- prefer stable internal IDs or a safe filename encoder for Git filenames;
- add regression tests for malicious widget names during Git status and commit serialization.
References
- GitHub Advisory: https://github.com/appsmithorg/appsmith/security/advisories/GHSA-r553-q33m-v7pf
- Appsmith project: https://github.com/appsmithorg/appsmith