{
  "parent_root_cause": "repoutil.UserPath (and RepositoryPath) join the owner/repository name under the configured repository root with filepath.Join without neutralising '..'; combined with the v1 organization-creation API accepting organization names containing '/' (api.CreateOrgOption has no AlphaDashDot and pre-fix CreateOrgForUser performs no inline validation), a crafted owner name escapes the repository root and a nested bare repository can be written inside another repository's local worktree. An executable Git hook pushed through Git smart-HTTP is materialised into the nested bare repo by a Gogs web-upload sync (UpdateLocalCopyBranch: git fetch + reset --hard); a git-receive-pack on the planted bare repo runs the hook as the Gogs service user (RCE).",
  "variant_root_cause": "Identical: the same uncleaned repoutil.UserPath on v0.14.2 is reached via a different, lower-privilege entry point — POST /api/v1/user/orgs (org.CreateMyOrg, reqToken() only, non-admin) -> CreateOrgForUser (no inline validation) -> CreateOrganization -> os.MkdirAll(repoutil.UserPath(org.Name)). The traversal owner name escapes the repository root into the same worktree sink (Repository.LocalCopyPath() = data/tmp/local-r/<id>), and the same hook-planting + git-receive-pack trigger yields RCE.",
  "shared_sink": "repoutil.UserPath / database.CreateOrganization (owner directory creation outside repository ROOT, inside <APP_DATA_PATH>/tmp/local-r/<id> worktree); UpdateLocalCopyBranch worktree sync materialising the executable post-update hook; git-receive-pack on the planted bare repo.",
  "shared_vulnerable_function": "internal/repoutil/repoutil.go:UserPath (v0.14.2: no pathutil.Clean)",
  "entry_point_difference": {
    "parent": "POST /api/v1/admin/users/:user/orgs (admin.CreateOrg) — requires admin token",
    "variant": "POST /api/v1/user/orgs (org.CreateMyOrg) — reqToken() only, any authenticated user (is_admin=0)"
  },
  "root_cause_equivalence": "same",
  "confidence": "high",
  "justification": "Both the parent and the variant exploit the absence of path-traversal sanitisation in repoutil.UserPath on v0.14.2, write the nested bare repository into the same worktree sink (data/tmp/local-r/<id>), plant the same executable post-update hook via the same Git smart-HTTP + web-upload sync primitive, and trigger execution with the same git-receive-pack step. The only material difference is the entry point (non-admin self-service org creation vs admin org creation), which lowers the required privilege. The fix (commit f6acd4673) closes both entry points (CreateOrgForUser inline AlphaDashDot covers admin.CreateOrg and org.CreateMyOrg) and the path layer (pathutil.Clean in UserPath).",
  "fix_commit": "f6acd467305943aae8403cbac81f0118dd1235d7",
  "fix_covers_variant": true,
  "is_bypass": false
}
