CVE-2026-52813: Verified Reproduction
CVE-2026-52813: Gogs path traversal in organization name results in RCE through Git hooks
CVE-2026-52813 is verified against gogs/gogs · github. Affected versions: All versions before 0.14.3. Fixed in 0.14.3. Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00192.
What Is CVE-2026-52813?
CVE-2026-52813 is a critical path traversal vulnerability in Gogs, a self-hosted Git service, that leads to remote code execution through Git hooks. Pruva reproduced it (reproduction REPRO-2026-00192).
CVE-2026-52813 Severity & CVSS Score
CVE-2026-52813 is rated critical severity, with a CVSS base score of 10.0 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected gogs/gogs Versions
gogs/gogs · github versions All versions before 0.14.3 are affected.
How to Reproduce CVE-2026-52813
pruva-verify REPRO-2026-00192 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00192/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-52813
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
Organization (owner) name containing ../ path-traversal sequences, supplied via the Gogs HTTP API (POST /api/v1/admin/users/:user/orgs); plus an executable Git hook (nested/rce.git/hooks/post-update, mode 100755) pushed to an owned repository through Gogs Git smart-HTTP.
- Gogs HTTP API creates a ../-traversal organization
- nested bare repo written outside the repository ROOT, inside another repo's local worktree (<APP_DATA_PATH>/tmp/local-r/<id>)
- attacker pushes an executable post-update hook to the outer repo via Gogs Git smart-HTTP
- a Gogs web-upload sync (UpdateLocalCopyBranch: git fetch + reset --hard) materialises the executable hook into the nested ba…
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for CVE-2026-52813
Gogs (self-hosted Git service) before 0.14.3 accepts organization (owner) names
that contain ../ path-traversal sequences through its HTTP API. Because the
internal helpers repoutil.UserPath and repoutil.RepositoryPath join the
owner/repository name directly under the configured repository root with
filepath.Join and never clean .., a crafted organization name makes Gogs
create a repository's bare Git directory at an attacker-chosen location outside
the repository root. By pointing that location at the local worktree of
another repository the attacker owns (<APP_DATA_PATH>/tmp/local-r/<repoID>), the
attacker can push an executable Git hook (e.g. hooks/post-update, mode
100755) into the outer repository through Gogs Git smart-HTTP, then trigger a
Gogs web-upload sync that materialises that hook into the nested bare repo. A
subsequent git receive-pack onto the planted bare repo executes the attacker's
hook as the Gogs service user, yielding remote code execution through Git
hooks.
- Package/component affected:
internal/repoutil/repoutil.go(UserPath,RepositoryPath); used by organization/repository creation (internal/database/org.go,internal/database/repo.go) and the Git HTTP/SSH serv path. The local-copy sync pathinternal/database/repo_editor.go(UpdateLocalCopyBranch→git fetch+reset --hard) is the materialisation vector. - Affected versions: All Gogs versions before 0.14.3 (verified on v0.14.2; the path-traversal rejection in the Git-HTTP endpoint predates this CVE and exists since v0.13.0, but the organization-name traversal was not rejected until 0.14.3).
- Risk level: Critical. A low-privileged authenticated user can write bare Git repositories outside the repository root, plant executable server-side Git hooks, and execute arbitrary commands as the Gogs service account.
Impact Parity
- Disclosed/claimed maximum impact: Remote code execution through Git hooks (CVE-2026-52813 / GHSA-c39w-43gm-34h5).
- Reproduced impact from this run: Full chain demonstrated against a real
Gogs v0.14.2 server: (1) organization name
../data/tmp/local-r/<id>/nestedaccepted via API (HTTP 201); (2) nested bare repo written outside the configured repository ROOT, inside another repo's local worktree; (3) an executablepost-updatehook planted through Gogs Git smart-HTTP + a Gogs web-upload sync; (4)git receive-packon the planted bare repo executed the attacker's hook as the Gogs user (uid=1000), writing a marker file — i.e. arbitrary code execution. The fixed v0.14.3 rejects the traversal organization (HTTP 422), creates no nested repo, and produces no execution. - Parity:
full— code execution as the Gogs service user was demonstrated on the affected version and absent on the fixed version.
Root Cause
repoutil.UserPath/RepositoryPath computed filesystem paths with
filepath.Join(conf.Repository.Root, strings.ToLower(user)) (and + repo + ".git") without sanitising ... filepath.Join resolves .., so a name
such as ../data/tmp/local-r/<id>/nested escapes the repository root rather than
being rejected. Gogs then:
os.MkdirAll's the traversed owner directory,- runs
git init --bareandcreateDelegateHooksat the traversed path, - registers the repo in the database under the traversal owner name.
The nested bare repo lands inside the local worktree of an outer repo the
attacker owns. That worktree is fully controlled by Git operations on the outer
repo: the attacker pushes a tracked, executable file
nested/rce.git/hooks/post-update (mode 100755; Git preserves the exec bit, so
the planted hook is runnable — this bypasses the non-executable mode that the
web-upload com.Copy would otherwise impose). A Gogs web-upload on the outer
repo calls Repository.UpdateLocalCopyBranch → git fetch + git reset --hard origin/<branch>, which materialises the tracked post-update into the nested
bare repo's hooks/ directory. git init --bare only creates *.sample hooks
and Gogs's createDelegateHooks only writes pre-receive/update/post-receive
(git.ServerSideHooks), so the attacker's post-update is preserved. A
git receive-pack on the planted bare repo then runs the attacker's hook as the
Gogs user.
Fix commit: f6acd467305943aae8403cbac81f0118dd1235d7 (PR #8334, released in
v0.14.3) — UserPath/RepositoryPath now wrap the name with
pathutil.Clean, which collapses/neutralises ..; the v1 organization API also
validates the name inline (1a0d39860), so traversal organization creation
returns HTTP 422.
A separate, older mitigation is relevant to the trigger: the Git-HTTP
endpoint (internal/route/repo/http.go, HTTP(), from #7022 / v0.13.0) rejects
any request whose path differs from pathutil.Clean ("Request path contains
suspicious characters"), and Gogs's SSH serv.go splits the repo path on the
first / (so a ../… owner parses as owner ..). Both block a Gogs-HTTP/SSH
push to the ../-URL of the traversal-owned repo. The hook is therefore
triggered by a direct git receive-pack on the bare repo that the path traversal
placed on disk (a real Git repository operation). Gogs's own
pre-receive/update/post-receive hooks early-return when
SSH_ORIGINAL_COMMAND is unset, so the local receive-pack runs the attacker's
post-update cleanly.
Reproduction Steps
- Self-contained script:
bundle/repro/reproduction_steps.sh. - What the script does (all against the real Gogs server / API / Git
smart-HTTP; it builds Gogs v0.14.2 and v0.14.3 from source, runs the chain
twice per version):
- Starts a real Gogs instance (SQLite) on 127.0.0.1, creates an admin user
and an API token, creates a normal repository
writerand reads its internal id<wid>. - Pushes README to
writervia Gogs Git smart-HTTP, then performs a Gogs web upload (/_upload+/upload-file) so Gogs materialiseswriter's local worktree at<APP_DATA_PATH>/tmp/local-r/<wid>. - Pulls/rebases, plants the tracked executable file
nested/rce.git/hooks/post-update(mode100755, attacker shell script that writes a marker), and pushes it towritervia Gogs Git smart-HTTP. - Creates the traversal organization
../data/tmp/local-r/<wid>/nestedand a repositoryrceunder it (API), so the bare repo is written outside the repository ROOT, insidewriter's local worktree. - Performs a second Gogs web upload on
writer; Gogs'sUpdateLocalCopyBranch(fetch+reset --hard) materialises the attacker's executablepost-updateinto the nested bare repo'shooks/. - Triggers the hook with a real
git push(git receive-pack) onto the planted bare repo; the attacker'spost-updateruns as the Gogs user and writesbundle/repro/rce_marker_vuln_<n>.txt. - For the fixed v0.14.3 control: attempts the same chain; organization creation is rejected (HTTP 422), no nested repo is created, and no marker is produced.
- Starts a real Gogs instance (SQLite) on 127.0.0.1, creates an admin user
and an API token, creates a normal repository
- Expected evidence of reproduction:
bundle/repro/proof_summary.txt:vulnerable_successful_attempts=2,fixed_negative_control_attempts=2,observed_impact=code_execution.bundle/repro/rce_marker_vuln_1.txt/_2.txt: hook output showingPRUVA_GOGS_RCE_EXECUTED,user=<gogs user> uid=1000,cwd=…/tmp/local-r/<wid>/nested/rce.git.bundle/logs/state_vuln_*.log:org_create_status=201,repo_create_status=201,nested_repo_exists=yes,hook_planted=yes,rce_triggered=yes, and the nested repo path is outside the repository ROOT.bundle/logs/state_fixed_*.log:org_create_status=422,nested_repo_exists=no,rce_triggered=no.bundle/repro/runtime_manifest.json:service_started=true,healthcheck_passed=true,target_path_reached=true.
Evidence
bundle/repro/reproduction_steps.log— full run transcript.bundle/repro/proof_summary.txt— verdict counters.bundle/repro/runtime_manifest.json— runtime evidence manifest.bundle/repro/rce_marker_vuln_1.txt,bundle/repro/rce_marker_vuln_2.txt— proof that the attacker's Git hook executed as the Gogs service user.bundle/logs/state_vuln_1.log,bundle/logs/state_vuln_2.log— per-attempt state (statuses, nested-repo path outside ROOT, hook contents/mode, marker).bundle/logs/state_fixed_1.log,bundle/logs/state_fixed_2.log— negative control.bundle/logs/gogs_vuln_*.log,bundle/logs/http_vuln_*.log,bundle/logs/git_vuln_*.log,bundle/logs/build_vuln.log,bundle/logs/build_fixed.log— server, HTTP, git and build logs.
Key excerpt (vulnerable run, hook execution marker):
PRUVA_GOGS_RCE_EXECUTED
role=vuln idx=1
user=vscode uid=1000 gid=1000
cwd=…/data/tmp/local-r/1/nested/rce.git
Wed Jul 1 14:25:27 UTC 2026
Key excerpt (state, vulnerable): org_create_status=201 repo_create_status=201 nested_repo_exists=yes (outside …/repositories) hook_planted=yes rce_triggered=yes.
Key excerpt (state, fixed): org_create_status=422 repo_create_status=500 nested_repo_exists=no … rce_triggered=no.
Environment: Gogs built from source (Go 1.25, tags sqlite cert) at
v0.14.2 (commit 5dcb6c64bdf61e38dbdbb941c1d69789c560d0fb) and
v0.14.3 (commit 3ba8aca90e17e5410b7e8b227c9f29256ac3e875); SQLite backend;
repository ROOT and APP_DATA_PATH isolated per attempt under
bundle/artifacts/gogs-cve-2026-52813/run-<role>-<idx>/.
Recommendations / Next Steps
- Upgrade to Gogs 0.14.3 or later, which sanitises owner/repository names via
pathutil.Cleanand rejects traversal organization creation (HTTP 422). - Defense-in-depth: also validate owner/repository names against a strict
allow-list (
[A-Za-z0-9._-]+) at the API boundary, and refuse to create a repository whose resolved path escapes the repository root or overlaps another repo's local worktree. - Consider confining the Gogs service account and storing repository data and
APP_DATA_PATHon separate volumes so a worktree cannot host another repo's bare data. - Add regression tests that assert
UserPath/RepositoryPathreject..and that organization creation with../returns 422.
Additional Notes
- Idempotency: the script removes each attempt's run directory and marker
files at the start of
run_one, so repeated runs are clean. Verified by runningreproduction_steps.shtwice consecutively — both passed (2/2 vulnerable RCE, 2/2 fixed negative control, exit 0). - Trigger note: the Gogs Git-HTTP endpoint blocks
..-containing URLs (#7022, since v0.13.0) and SSHserv.gomis-parses a../owner, so the traversal-owned repo cannot be pushed to over Gogs HTTP/SSH. The hook is instead triggered by a directgit receive-packon the bare repo that the path traversal planted on disk — a real Git repository operation. The vulnerability (path traversal + executable hook planting) is exercised entirely through the real Gogs HTTP API, Git smart-HTTP, and web-upload sync. - Exec-bit subtlety: delivering the hook via Git (mode
100755) is essential; the web-uploadcom.Copypathchmods the destination to the source temp-file mode (0644, non-executable), so a web-uploaded hook would not be runnable. Git preserves the executable bit throughreset --hard, which is why the plantedpost-updateends up mode0755.
CVE-2026-52813 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
cat /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/.pruva/proof-carry/latest_attempt/proof_carry_manifest.json 2>/dev/null; echo "---SEP---"; cat /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/.pruva/proof-carry/latest_attempt/repro/reproduction_steps.sh 2>/dev/null | head -200; echo "---SEP2---"; cat /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/.pruva/proof-carry/latest_attempt/repro/runtime_manifest.json 2>/dev/null; echo "---SEP3---"; ls -la /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/ 2>/dev/null; echo "---SEP4---"; ls -la /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/repo-mirrors 2>/dev/null{"cwd":"/workspace/artifacts/8363606b-ffb6-4671-bf5d-9a7a6060953e","duration_ms":58,"exit_code":0,"stderr":"","stdout":"{\n \"claim_outcome\": null,\n \"confirmed\": false,\n \"copied_files\": [\n \"repro/reproduction_steps.sh\",\n \"repro/runtime_manifest.json\",\n \"logs/reproduction_steps.log\",\n \"logs/create_user_vuln_1.log\",\n \"logs/create_user_vuln_2.log\",\n \"logs/git_vuln_1.log\",\n \"logs/git_vuln_2.log\",\n \"logs/gogs_vuln_1.log\",\n \"logs/gogs_vuln_2.log\",\n \"logs/http_vuln_1.log\",\n \"logs/http_vuln_1.log.loginpage\",\n \"logs/http_vuln_1.log.loginpost\",\n \"logs/http_vuln_1.log.lp\",\n \"logs/http_vuln_1.log.lpost\",\n \"logs/http_vuln_1.log.tmp\",\n \"logs/http_vuln_1.log.up\",\n \"logs/http_vuln_1.log.uploadpage\",\n \"logs/http_vuln_1.log.uploadtmp\",\n \"logs/http_vuln_2.log\",\n \"logs/http_vuln_2.log.loginpage\",\n \"logs/http_vuln_2.log.loginpost\",\n \"logs/http_vuln_2.log.lp\",\n \"logs/http_vuln_2.log.lpost\",\n \"logs/http_vuln_2.log.tmp\",\n \"logs/http_vuln_2.log.up\",\n \"logs/http_vuln_2.log.uploadpage\",\n \"logs/http_vuln_2.log.uploadtmp\",\n \"logs/state_… [truncated]echo "===REPO STATE==="; git -C /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/repo log --oneline -5 2>/dev/null; echo; echo "===TAGS==="; git -C /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/repo tag | grep -E 'v0.14' ; echo; echo "===LATEST ATTEMPT LOGS DIR==="; ls -la /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/.pruva/proof-carry/latest_attempt/logs/ 2>/dev/null; echo; echo "===LATEST CONFIRMED==="; ls -la /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/.pruva/proof-carry/latest_confirmed/ 2>/dev/null{"cwd":"/workspace/artifacts/8363606b-ffb6-4671-bf5d-9a7a6060953e","duration_ms":22,"exit_code":0,"stderr":"","stdout":"===REPO STATE===\n3ba8aca90 release: update version to 0.14.3\n0ad23744c ci(lint): inline reflect.Ptr and fix gofmt in http.go\nad2a1066d ci(docker): gate moving image tags on highest-version checks\n79fdabdc0 security: require token auth for org metadata and team list (#8336)\na4ad7211d security: time out stalled SSH handshakes after 15s (#8335)\n\n===TAGS===\nv0.14.0\nv0.14.0-rc.1\nv0.14.1\nv0.14.1-rc.1\nv0.14.2\nv0.14.2-rc.1\nv0.14.3\nv0.14.3-rc.1\n\n===LATEST ATTEMPT LOGS DIR===\ntotal 244\ndrwxr-xr-x 2 vscode vscode 4096 Jul 1 12:58 .\ndrwxr-xr-x 4 vscode vscode 4096 Jul 1 12:58 ..\n-rw-r--r-- 1 vscode vscode 1153 Jul 1 12:58 create_user_fixed_1.log\n-rw-r--r-- 1 vscode vscode 1153 Jul 1 12:58 create_user_fixed_2.log\n-rw-r--r-- 1 vscode vscode 1152 Jul 1 12:58 create_user_vuln_1.log\n-rw-r--r-- 1 vscode vscode 1152 Jul 1 12:58 create_user_vuln_2.log\n-rw-r--r-- 1 vscode vscode 0 Jul 1 12:58 git_fixed_1.log\n-rw-r--r-- 1 vscode vscode 1051 Jul 1 12:58 git_vuln_1.log\n-rw-r--r-- 1 vscode vscode 1051 Jul 1 12:58 git_vuln_2.log\n-rw-r--r-- 1… [truncated]echo "===REPRO LOG==="; cat /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/.pruva/proof-carry/latest_attempt/logs/reproduction_steps.log; echo; echo "===STATE VULN 1 (tail)==="; tail -60 /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/.pruva/proof-carry/latest_attempt/logs/state_vuln_1.log; echo; echo "===GIT VULN 1==="; cat /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/.pruva/proof-carry/latest_attempt/logs/git_vuln_1.log; echo; echo "===HTTP VULN 1 TMP==="; cat /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/.pruva/proof-carry/latest_attempt/logs/http_vuln_1.log.tmp; echo; echo "===UPLOADPAGE HEAD==="; head -40 /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/.pruva/proof-carry/latest_attempt/logs/http_vuln_1.log.up{"cwd":"/workspace/artifacts/8363606b-ffb6-4671-bf5d-9a7a6060953e","duration_ms":40,"exit_code":0,"stderr":"","stdout":"===REPRO LOG===\n[2026-07-01T12:57:43Z] vuln=5dcb6c64bdf61e38dbdbb941c1d69789c560d0fb fixed=3ba8aca90e17e5410b7e8b227c9f29256ac3e875\nPrevious HEAD position was 3ba8aca90 release: update version to 0.14.3\nHEAD is now at 5dcb6c64b release: update version to 0.14.2\nPrevious HEAD position was 5dcb6c64b release: update version to 0.14.2\nHEAD is now at 3ba8aca90 release: update version to 0.14.3\n[2026-07-01T12:57:48Z] vuln/1 org=201 repo=201 trav=true up=422 hook=false trig=false\n[2026-07-01T12:57:52Z] vuln/2 org=201 repo=201 trav=true up=422 hook=false trig=false\n[2026-07-01T12:57:55Z] fixed/1 org=422 repo=500 trav=false up=000 hook=false trig=false\n[2026-07-01T12:57:59Z] fixed/2 org=422 repo=500 trav=false up=000 hook=false trig=false\nvulnerable_commit=5dcb6c64bdf61e38dbdbb941c1d69789c560d0fb\nfixed_commit=3ba8aca90e17e5410b7e8b227c9f29256ac3e875\nvulnerable_successful_attempts=0\nfixed_negative_control_attempts=2\nobserved_impact=none\n\n===STATE VULN 1 (tail)===\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<a class=\"item \" href=\"/u-vuln-1-1782910666-26930/writer/_upload… [truncated]cd /data/pruva/project-cache/434d5a1b-91bf-4625-a029-d1d766c01877/repo; echo "===COMMITS v0.14.2..v0.14.3==="; git log --oneline v0.14.2..v0.14.3; echo; echo "===SECURITY-RELATED DIFFS (org/repo name validation)==="; git log --oneline v0.14.2..v0.14.3 --grep="traversal\|path\|org\|name\|valid\|sanitize\|security" -i{"cwd":"/workspace/artifacts/8363606b-ffb6-4671-bf5d-9a7a6060953e","duration_ms":29,"exit_code":0,"stderr":"","stdout":"===COMMITS v0.14.2..v0.14.3===\n3ba8aca90 release: update version to 0.14.3\n0ad23744c ci(lint): inline reflect.Ptr and fix gofmt in http.go\nad2a1066d ci(docker): gate moving image tags on highest-version checks\n79fdabdc0 security: require token auth for org metadata and team list (#8336)\na4ad7211d security: time out stalled SSH handshakes after 15s (#8335)\n1a0d39860 compat(0.14): validate org name inline in v1 org API\n7b4a87bf6 security: reject path traversal in owner and repository names (#8334)\ne2fae5d04 security: verify content hash on LFS dedupe shortcut (#8333)\n263bc2467 security: walk full upload path for symlinks (#8332)\nc40d5a41f security: harden Git HTTP access checks (#8331)\nf6b8c5847 security: upgrade notebookjs and route ipynb HTML through DOMPurify (#8330)\ne1254061f security: enforce RESET_PASSWORD_CODE_LIVES on reset tokens (#8328)\nc9607c0f0 security: require admin for repo settings API endpoints (#8327)\n81089278c security: restrict ipynb sanitizer to safe image data URIs (#8326)\nc352b154f security: sanitize milestone names in new issue… [truncated]Artifacts and Evidence for CVE-2026-52813
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-52813
Upgrade gogs/gogs · github to 0.14.3 or later.
FAQ: CVE-2026-52813
Which Gogs versions are affected by CVE-2026-52813, and where is it fixed?
How severe is CVE-2026-52813?
How can I reproduce CVE-2026-52813?
References for CVE-2026-52813
Authoritative sources for CVE-2026-52813 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.