← back to lessons

Cycle369 - Gogs CVE-2026-52811 (asymmetric symlink check) does NOT port to Forgejo - WALK

Date: 2026-07-15 Class: symlink path-traversal / guard-inconsistency port-lag probe Verdict: WALK CLEAN (upstream vector structurally absent in the fork)

The probe

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).

Why it does NOT port (two independent reasons)

  1. Index-based editing, not worktree-based. Forgejo's repo file create/update/upload (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.
  2. Explicit per-component symlink rejection anyway. 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.

Reusable lesson (bank)

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.

Disposition

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.

Generated 2026-08-08 05:45:05 UTC | auto-sync /15min