CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-20912

Gitea Attachment Smuggling: The Private-to-Public Pipeline

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 24, 2026·7 min read·46 visits

Executive Summary (TL;DR)

Gitea failed to verify that an attachment belongs to the repository where it is being linked. An attacker with access to a private repository can take the UUID of a sensitive attachment and 'adopt' it into a public release or issue in a different repository. This exposes private files (binaries, configs, datasets) to the public internet. Fixed in version 1.25.4.

A critical logic flaw in Gitea's attachment handling allows authenticated users to link files from private repositories to public releases, effectively bypassing access controls and exposing sensitive data to the internet.

The Hook: When Is a File Not Your File?

Gitea is the darling of the self-hosted Git world. It's written in Go, it's lightweight, and it generally does a good job of keeping the doors locked. But like many modern web applications, it suffers from a common architectural headache: the disconnect between an object's existence and its context. In the world of database-driven applications, we often assign unique identifiers (UUIDs) to everything—users, repos, issues, and, crucially for this story, attachments.

Attachments in Gitea are those binary blobs you drag and drop into an issue comment or a release page. They aren't Git objects; they live in a separate storage backend (local disk, S3, etc.) and are referenced by a database entry. When you upload a file, Gitea gives it a UUID and stamps it with a RepoID to remember where it came from. So far, so good.

But here is the million-dollar question: When you tell Gitea, "Hey, attach file UUID-1234 to Release v1.0 in Repository B," does Gitea check if UUID-1234 actually belongs to Repository B? Or does it just check if UUID-1234 exists? If you guessed the latter, congratulations. You've just found a Critical vulnerability. This is a classic logic flaw where the application trusts the ID provided by the user without validating the relationship between that ID and the current context.

The Flaw: IDOR with a Twist

Technically, this falls under the umbrella of Insecure Direct Object Reference (IDOR), or specifically CWE-639 (Authorization Bypass Through User-Controlled Key). The flaw wasn't in the authentication layer—you still needed to be logged in. The flaw was in the authorization context of the attachment handler.

The vulnerability stems from how Gitea handles the "Edit Release" or "Edit Issue" actions. When you edit a release, the frontend sends a list of attachment UUIDs that should be associated with that release. The backend iterates through this list to update the database.

The logic went something like this:

  1. User sends request to update Release X in Repo A.
  2. User includes attachment UUID-Z in the payload.
  3. Server looks up UUID-Z. Does it exist? Yes.
  4. Server links UUID-Z to Release X.

The missing step? Checking if UUID-Z belongs to Repo A.

This oversight allows for what I call "Attachment Smuggling." If I have read access to a private repository (Repo P), I can see the UUIDs of its attachments. If I also have write access to a public repository (Repo Public)—which is trivial since I can just create one—I can take the UUID from the private repo and tell Gitea to attach it to my public release. The database updates, the link is created, and suddenly that private tax document or proprietary binary is being served publicly from my innocuous-looking repo.

The Code: The Missing 'If' Statement

Let's look at the smoking gun. The vulnerability lived in routers/web/repo/attachment.go. The code was responsible for handling attachment updates but was too trusting of the input.

The Vulnerable Logic (Conceptual):

// OLD CODE (Vulnerable)
func UpdateReleaseAttachments(ctx *context.Context) {
    // Get the attachment ID from the request
    attachID := ctx.FormString("attachment_uuid")
    
    // Load the attachment from DB
    attach, err := models.GetAttachmentByUUID(attachID)
    if err != nil {
        // Handle error
    }
    
    // DANGER: We proceed to link the attachment without checking ownership!
    attach.ReleaseID = ctx.Release.ID
    models.UpdateAttachment(attach)
}

The developers fixed this in Pull Request #36320 by adding a strict ownership check. They realized that just because an attachment exists doesn't mean the current context has the right to move it.

The Fix (Patched):

// NEW CODE (Fixed in 1.25.4)
func UpdateReleaseAttachments(ctx *context.Context) {
    // ... load attachment ...
 
    // THE FIX: Ensure the attachment belongs to the current repository
    if attach.RepoID != ctx.Repo.Repository.ID {
        ctx.HTTPError(http.StatusBadRequest, "attachment does not belong to this repository")
        return
    }
    
    // ... proceed ...
}

This simple condition attach.RepoID != ctx.Repo.Repository.ID completely kills the attack class. It enforces a boundary: objects created in Repo A stay in Repo A. It effectively segregates the object references by their parent container.

The Exploit: Smuggling Secrets

To exploit this, an attacker needs two things: read access to a victim's private repo (perhaps as a contractor or low-level employee) and write access to any public repo.

Step 1: Reconnaissance The attacker navigates to the target private repository, for example, Company/TopSecretProject. They find a release or an issue containing a sensitive attachment, say credentials.json or proprietary_firmware.bin. By inspecting the network traffic (Developer Tools > Network) or the HTML source, they locate the attachment's UUID. It looks something like a1b2c3d4-e5f6-7890-1234-567890abcdef.

Step 2: The Setup The attacker creates a new public repository, Attacker/Free-Wallpapers. They create a new Release titled "v1.0".

Step 3: The Swap The attacker intercepts the HTTP request used to create or edit the release in their public repo. The payload will contain a field for attachments. They inject the stolen UUID from Step 1:

POST /Attacker/Free-Wallpapers/releases/new
{
  "tag_name": "v1.0",
  "title": "Cool Wallpaper",
  "files": [
    "a1b2c3d4-e5f6-7890-1234-567890abcdef" // <--- The UUID from the PRIVATE repo
  ]
}

Step 4: The Payload The server processes the request. It sees the UUID, grabs the file pointer from the storage backend, and links it to the Attacker/Free-Wallpapers release. Now, anyone on the internet can visit the attacker's public release page and download the proprietary_firmware.bin directly, bypassing the authentication checks that should have protected the original private repository.

The Impact: Why This Matters

This vulnerability is rated Critical (9.1) for a reason. It completely undermines the "Private" setting on a repository. In a corporate environment, Gitea is often used to host internal tools, configuration files with embedded secrets, or source code that isn't meant for the public eye.

Consider a scenario where a CI/CD pipeline uploads build artifacts to a private Gitea release. These artifacts might contain debug symbols, hardcoded API keys, or intellectual property. An insider threat—or someone who has compromised a low-level account—can use this vulnerability to exfiltrate gigabytes of data without triggering typical "mass download" alarms, because the traffic looks like legitimate usage of the public interface.

Furthermore, because the file is now hosted on a public URL, the attacker doesn't just download it; they can share the link with others. The file is effectively "laundered" through the public repository.

The Fix: Remediation

The fix is straightforward: Update to Gitea 1.25.4. The patch introduces the necessary validation logic to prevent cross-repository attachment linking.

If you cannot upgrade immediately, you are in a tight spot. There are no easy configuration toggles to disable this specific behavior. You would need to implement a Web Application Firewall (WAF) rule that inspects the body of POST requests to release/issue endpoints, but validating UUID ownership at the WAF level is practically impossible without querying the database.

Post-Exploitation Forensics: If you suspect this has happened, you need to query your database. You can run a SQL query joining the attachment table with the repository table (via releases or issues) to find discrepancies.

-- Pseudo-SQL to find 'smuggled' attachments
SELECT * FROM attachment a
JOIN release r ON a.release_id = r.id
WHERE a.repo_id != r.repo_id;

If that query returns rows, someone has linked an attachment from one repo to a release in another. Investigate immediately.

Official Patches

GiteaGitea v1.25.4 Release Notes
GiteaPull Request #36320 (Fix)

Fix Analysis (1)

Technical Appendix

CVSS Score
9.1/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
EPSS Probability
0.02%
Top 97% most exploited

Affected Systems

Gitea Server

Affected Versions Detail

Product
Affected Versions
Fixed Version
Gitea
Gitea
<= 1.25.31.25.4
AttributeDetail
CWE IDCWE-639
Attack VectorNetwork
CVSS Score9.1 (Critical)
EPSS Score0.00017
ImpactHigh Confidentiality Loss
Exploit StatusNo Public PoC

MITRE ATT&CK Mapping

T1213Data from Information Repositories
Collection
T1596Search Open Technical Databases
Reconnaissance
CWE-639
Authorization Bypass Through User-Controlled Key

Authorization Bypass Through User-Controlled Key

Known Exploits & Detection

N/ANo public automated exploit script available at this time.

Vulnerability Timeline

Fix commit merged to master
2026-01-12
Gitea 1.25.4 Released
2026-01-22
CVE Published
2026-01-22

References & Sources

  • [1]GHSA Advisory
  • [2]Gitea Blog Post

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•4 minutes ago•GHSA-WCMJ-X466-56MM
6.1

GHSA-WCMJ-X466-56MM: Arbitrary File Write via UNIX Symbolic Link Following in OpenTofu

A UNIX symbolic link following vulnerability exists in the provider cache installation mechanism of OpenTofu. This flaw allows an attacker with control over the repository files to write files outside of the intended workspace boundary during initialization.

Amit Schendel
Amit Schendel
0 views•6 min read
•about 2 hours ago•CVE-2026-48507
7.1

CVE-2026-48507: Incorrect Authorization in Snipe-IT Bulk User Edit and Merge Features

An incorrect authorization vulnerability (CWE-863) in Snipe-IT versions prior to 8.6.0 allows authenticated, low-privileged users with granular 'users.edit' permissions to modify restricted user flags ('activated' and 'ldap_import') and merge high-privileged administrator accounts into standard user accounts. This allows an attacker to lock administrators out of the system or completely hijack administrator accounts.

Amit Schendel
Amit Schendel
2 views•8 min read
•about 2 hours ago•GHSA-W2J7-F3C6-G8CW
4.7

GHSA-w2j7-f3c6-g8cw: Open Redirect Bypass via Parser Differential in Flask-Security

An open redirect vulnerability exists in Flask-Security versions up to and including 5.8.0. This flaw allows remote, unauthenticated attackers to perform open redirects by exploiting a parser differential between Python's standard library urlsplit() function and modern web browsers when subdomain redirection is allowed.

Amit Schendel
Amit Schendel
2 views•8 min read
•about 5 hours ago•CVE-2026-49205
6.5

CVE-2026-49205: Missing Authorization in phpMyFAQ Public REST API Write Endpoints

An incomplete security patch for CVE-2026-24421 in phpMyFAQ allows authenticated low-privileged users to bypass role-based access controls. While the initial patch addressed missing authorization in the BackupController, it left four critical write-enabled endpoints vulnerable. This allows remote attackers with a valid low-privilege API token to perform unauthorized data modifications, creating categories, creating FAQs, updating FAQs, and injecting questions directly into the database.

Amit Schendel
Amit Schendel
6 views•5 min read
•about 14 hours ago•GHSA-74P7-6H78-GW8P
8.6

GHSA-74P7-6H78-GW8P: Multiple Critical Security Flaws in skillctl Agent-Skill Manager

An in-depth security audit of the skillctl command-line package manager revealed five critical and high-severity security vulnerabilities. The identified flaws span parameter-level command argument injection via the source_sha parameter, uncontrolled resource consumption (Denial of Service) through unnamed UNIX FIFOs and character devices, directory path traversal in the destination argument, commit-message trailer forgery via newline injection in skill names, and local credential exfiltration leveraging UNIX hardlinks. These vulnerabilities represent significant vectors for workstation compromise when executing agentic tasks in repositories containing untrusted files or pull requests. Remediation was introduced in version v0.1.3.

Alon Barad
Alon Barad
6 views•6 min read
•about 18 hours ago•CVE-2026-48153
8.5

CVE-2026-48153: Server-Side Request Forgery in Budibase OAuth2 SDK

CVE-2026-48153 is a Server-Side Request Forgery (SSRF) vulnerability in the Budibase OAuth2 SDK prior to version 3.39.0. It allows authenticated low-privileged users to bypass outbound network security blacklists and send arbitrary requests to internal subnets or cloud metadata services.

Alon Barad
Alon Barad
10 views•7 min read