Date: 2026-07-15 Class: symlink path-traversal / guard-inconsistency port-lag probe Verdict: WALK CLEAN (upstream vector structurally absent in the fork)
Gogs CVE-2026-52811 = UploadRepoFiles checks only the LEAF filename for a symlink
while sibling handlers use hasSymlinkInPath (every-component lstat). Classic
guard-inconsistency. Probed whether the same file-edit-follows-on-disk-symlink
vector ports to Forgejo (shared Gitea/Gogs ancestry, my established 7-finding lane).
services/repository/files/{upload,update}.go) writes user content ONLY through git
plumbing: HashObject (content -> blob in the object DB) then
AddObjectToIndex(mode, hash, path) (git index), then WriteTree/commit.
upload.go:231 hardcodes mode 100644 (regular file). There is NO
os.OpenFile/Create/WriteFile(filepath.Join(worktree, userControlledPath))
anywhere in the package - grep-confirmed empty. The Gogs bug REQUIRES such an
on-disk worktree write for the symlinked path component to redirect the write
outside the repo. Forgejo has no such write to redirect.update.go:~353 walks each
tree-path component and returns ErrFilePathInvalid ("a symbolic link exists
where you're trying to create a subdirectory") if any component entry.IsLink().
So Forgejo even guards the git-tree-symlink case that Gogs' leaf-only check missed.
Plus pathutils.go SanitizePath path.Cleans ../ components.Architecture immunity beats guard-parity when porting a symlink-write CVE.
Before diffing per-component-vs-leaf symlink checks across a fork, first ask HOW the
target writes the user file:
- On-disk worktree write (os.OpenFile(join(worktree, userPath))) = vulnerable
substrate; the symlink-in-path check is the ONLY defense -> guard-inconsistency matters.
- Git-index/plumbing write (hash-object + update-index) = structurally immune;
content becomes a content-addressed blob, path becomes an index entry, git never
follows an OS symlink to place it. No amount of path symlinking escapes the object DB.
Gogs uses the worktree substrate; Forgejo (like modern Gitea) uses the index substrate.
The same CVE class simply cannot exist on the index substrate. Check the substrate
FIRST - it can save a full guard-parity diff.
Gogs itself = SATURATED/unmaintained (banked cycle324) - do not mine. Forgejo lane unchanged: the 7 cycle298 port-lag gaps remain the fileable set; this probe added no 8th. Standing operator action unchanged: file Forgejo x7 + Incus x1 + cline x1 + Amazon Q x3.