Search: t**n*e**l s.a. c***t**c***a
1,600 threats
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| High | 7.8 | EPSS 0.5% | High7.8EPSS 0.5%GCVE DatabaseCVE-2026-64600 In the Linux kernel, the following vulnerability has been resolved: xfs: resample the data fork mapping after cycling ILOCK xfs_reflink_fill_{cow_hole,delalloc} are both presented with an inode, a data fork mapping, and a cow fork mapping. Unfortunately, these two helpers cycle the ILOCK to grab a transaction, which means that the mappings are stale as soon as we reacquire the ILOCK. Currently we refresh the cow fork mapping by re-calling xfs_find_trim_cow_extent, but we don't refresh the data fork mapping beforehand, which means that the xfs_bmap_trim_cow in that function queries the refcount btree about the wrong physical blocks and returns an inaccurate value in *shared. If *shared is now false, the directio write proceeds with a stale data fork mapping. Fix this by querying the data fork mapping if the sequence counter changes across the ILOCK cycle. | GCVE Database | ||
| Medium | 6.5 | EPSS 0.4% | Medium6.5EPSS 0.4%GCVE Database # Security Vulnerability Report: STOMP CONNECT Frame Header Injection in Netty ## 1. Vulnerability Summary | Field | Value | |-------|-------| | **Product** | Netty | | **Version** | 4.2.12.Final (and all prior versions with codec-stomp) | | **Component** | `io.netty.handler.codec.stomp.StompSubframeEncoder` | | **Vulnerability Type** | CWE-93: Improper Neutralization of CRLF Sequences / CWE-113: Improper Neutralization of CRLF in HTTP Headers | | **Impact** | STOMP Header Injection / Authentication Bypass | | **CVSS 3.1 Score** | **6.5 (Medium)** | | **CVSS 3.1 Vector** | `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N` | | **Attack Vector** | Network | | **Attack Complexity** | Low | | **Privileges Required** | Low | | **User Interaction** | None | | **Scope** | Unchanged | | **Confidentiality Impact** | None | | **Integrity Impact** | High | | **Availability Impact** | None | ## 2. Affected Components - `io.netty.handler.codec.stomp.StompSubframeEncoder` — `encodeHeaders()` method (lines 174-200) - `io.netty.handler.codec.stomp.StompSubframeEncoder` — `shouldEscape()` method (lines 214-216) ## 3. Vulnerability Description The Netty STOMP codec encoder (`StompSubframeEncoder`) intentionally skips the `escape()` function for `CONNECT` and `CONNECTED` commands. This means that newline characters (`\n`) in header values of CONNECT frames are written directly to the output, allowing an attacker to inject additional STOMP headers. ### Root Cause In `StompSubframeEncoder.java`, the `shouldEscape()` method (lines 214-216) explicitly excludes CONNECT and CONNECTED commands from escaping: ```java private static boolean shouldEscape(StompCommand command) { return command != StompCommand.CONNECT && command != StompCommand.CONNECTED; } ``` When `shouldEscape()` returns `false`, header values are written without any escaping (line 195): ```java CharSequence headerValue = shouldEscape ? escape(entry.getValue()) : entry.getValue(); ByteBufUtil.writeUtf8(buf, headerValue); // Raw \n written to output buf.writeByte(StompConstants.LF); ``` For other commands (SEND, SUBSCRIBE, etc.), the `escape()` method (lines 218-240) correctly converts `\n` to `\\n`, `\r` to `\\r`, `:` to `\\c`, and `\\` to `\\\\`. ### STOMP Specification Context and Security Analysis The STOMP 1.2 specification (Section 10, Value Encoding) states that CONNECT and CONNECTED frames should **not use escaping**, to maintain backwards compatibility with STOMP 1.0 clients that do not understand escape sequences. **However, "no escaping" does not mean "no validation".** The specification's intent is that CONNECT headers should not use the `\n` → `\\n` escape notation. It does **not** mandate that implementations must accept raw newline characters within header values. There is a critical distinction: - **Escaping** = converting `\n` to `\\n` in the wire format (spec says: don't do this for CONNECT) - **Validation** = rejecting header values that contain `\n` (spec does not prohibit this) Netty's implementation conflates these two concepts: by skipping `escape()`, it also skips **all** protection against newline injection. The correct behavior would be to skip escaping but still **reject** values containing raw newline characters, since such values are inherently malformed — no legitimate STOMP 1.0 or 1.2 header value should contain a raw `\n`. This is analogous to Netty's own SMTP fix (GHSA-jq43-27x9-3v86): SMTP parameters don't need escaping either, but Netty added validation to reject CRLF in parameters. The same principle should apply here. **Additionally**, Netty's own test suite explicitly validates this non-escaping behavior in `StompSubframeEncoderTest.java:126-143` (`testNotEscapeStompHeadersForConnectCommand`), confirming that this is a deliberate design choice — but the test only verifies that escaping is skipped, not that injection is possible. The security implications were not considered. **Summary**: The vulnerability exists because: 1. Header values in CONNECT frames are **neither escaped nor validated** for newlines 2. A raw newline in a header value creates a **new header line** on the wire 3. The STOMP broker parses each line as a separate header 4. The fix should **validate** (reject `\n`) rather than **escape** (convert `\n` to `\\n`), maintaining spec compliance ## 4. Exploitability Prerequisites This vulnerability is exploitable when **all** of the following conditions are met: 1. The application uses Netty's `codec-stomp` module to encode STOMP frames 2. User-controlled input is placed into header values of a `CONNECT` or `CONNECTED` frame 3. The application does **not** perform its own newline sanitization 4. The downstream STOMP broker processes the injected headers (broker-dependent) **Typical affected use cases**: - STOMP proxy/gateway applications that forward or construct CONNECT frames with user-supplied credentials - Web-to-STOMP bridge applications (e.g., WebSocket-STOMP proxies) where login/passcode | GCVE Database | ||
| High | 8.4 | EPSS 0.2% | CVE-2026-14881MongoDB Compass · OS Command Injection High8.4EPSS 0.2%CVE Database V5CVE-2026-14881 When importing connections in Compass it is possible to override some connection options that are otherwise can't be changed via connection form. In particular it is possible to provide a custom browser open command for OIDC auth flow that is usually can be set only globally via Compass settings. | CVE Database V5 | ||
| –Not rated | – | Reddit NetSec This report describes a paid bug-bounty-style game conducted against a multimodal prompt firewall designed to detect prompt injection and jailbreak attempts targeting large language models (LLMs). The project includes open-source code, a fine-tuned DeBERTa-v3-large binary classifier model, and a dataset of 13,230 real-world attack attempts collected over a year. The detector uses a two-stage approach combining regex pattern matching and machine learning inference to filter malicious inputs across multiple modalities including text, images, documents, and audio. The dataset and tooling are intended as a mitigation layer rather than a complete solution, and the author encourages further testing and improvement. No direct vulnerability or exploit is reported, and no patch or fix is applicable as this is a defensive research artifact. | Reddit NetSec | |||
| Critical | 9.1 | EPSS 0.4% | Critical9.1EPSS 0.4%GCVE DatabaseCVE-2026-16645 The Photoswipe Drupal module provides integration for the widely used [PhotoSwipe lightbox library](https://photoswipe.com/) to display / zoom images in lightbox galleries using the provided image formatters. The module didn't sufficiently check access permissions, when viewing an image using the photoswipe image gallery display formatter, in versions < 3.0.4 (Drupal 8) or < 3.2.0 (Drupal 9 / Drupal 10). This vulnerability is mitigated by the fact that it only affects sites limiting access to the images shown in photoswipe (the most common use case for photoswipe lightboxes public images). | GCVE Database | ||
| Critical | 9.1 | EPSS 0.4% | CVE-2026-16644Webform rest · This module enables you to retrieve and submit webform submissions via REST endpoints Critical9.1EPSS 0.4%GCVE DatabaseCVE-2026-16644 This module enables you to retrieve and submit webform submissions via REST endpoints. The module doesn't sufficiently check the parent webform's permissions for creating, viewing and updating permissions. This vulnerability is mitigated by the fact that an attacker must already have permissions to use the rest resource. This advisory only affects already-unsupported versions 4.0.3 and earlier. | GCVE Database | ||
| Medium | 6.1 | EPSS 0.3% | CVE-2026-16640Search api autocomplete · This module enables you to add autocomplete suggestions for search forms Medium6.1EPSS 0.3%GCVE DatabaseCVE-2026-16640 This module enables you to add autocomplete suggestions for search forms created with the [Search API module](/project/search_api). The module ships with a test script that is accessible to anonymous users and doesn't sufficiently validate user input, leading to a Cross Site Scripting vulnerability. This vulnerability is mitigated by the fact that the web server must be configured to display warning messages to users. | GCVE Database | ||
| Critical | 9.8 | EPSS 0.6% | CVE-2026-16639drupal/i18n_sso · I18n sso: Critical9.8EPSS 0.6%GCVE DatabaseCVE-2026-16639 In a scenario of a multilingual website with different domain names per language, this module enables you to be automatically connected across the language domains if you are logged on the main language domain. The module doesn't sufficiently validate a short-lived token, allowing an attacker to bypass access control and authenticate as a victim user. This vulnerability is mitigated by the fact that an attacker must appear to originate from the same client IP as the victim. | GCVE Database | ||
| Medium | 6.1 | EPSS 0.3% | CVE-2026-16638Media folders · This module provides a better UI for managing and selecting Media entities in a folder Medium6.1EPSS 0.3%GCVE DatabaseCVE-2026-16638 This module provides a better UI for managing and selecting Media entities in a folder structure. The module doesn't sufficiently sanitize the names and descriptions of media items and folders when they are displayed in the media browser, resulting in a stored cross-site scripting (XSS) vulnerability. This vulnerability is mitigated by the fact that an attacker must have a role with permission to create or edit media items or folders. | GCVE Database | ||
| –Not rated | – | SANS ISC Handlers Diary This isn&#;x26;#;39;t a new attack, but something I saw "pop-up" in our logs this week:
 | SANS ISC Handlers Diary | |||
| Medium | 5.3 | EPSS 0.4% | Medium5.3EPSS 0.4%CVE Database V5CVE-2026-16560 A heap-buffer-overflow flaw was found in Directory Server (389-ds-base). When a DN contains a legacy-quoted value, the server won't close the heap allocation allowing another call to refer to the same memory pointer causing a denial of service or an arbitrary memory write operation. EPSS 0.4%Vulnerability | CVE Database V5 | ||
| High | 8.2 | EPSS 0.3% | CVE-2026-62456Oracle HRMS (UK) · Improper Access Control High8.2EPSS 0.3%CVE Database V5CVE-2026-62456 CVE-2026-62456 is a high-severity vulnerability in Oracle HRMS (UK) component of Oracle E-Business Suite versions 12.2.3 through 12.2.15. It allows a low privileged attacker with network access via HTTPS to compromise the product, potentially leading to unauthorized creation, deletion, or modification of critical data. The vulnerability also has a scope change impact, affecting additional Oracle products. The CVSS 3.1 base score is 8.2, reflecting high confidentiality and integrity impacts. No official patch or remediation level is currently confirmed by Oracle. The vendor strongly recommends applying security patches promptly as part of their Critical Patch Update process. No known exploits in the wild have been reported to date. | CVE Database V5 | ||
| High | 8.2 | EPSS 0.2% | High8.2EPSS 0.2%CVE Database V5CVE-2026-61101 CVE-2026-61101 is a high-severity vulnerability in Oracle MES for Process Manufacturing versions 12.2.3 through 12.2.15. It allows an unauthenticated attacker with network access via HTTP to compromise the product, requiring user interaction from a person other than the attacker. Successful exploitation can lead to unauthorized access to critical data and unauthorized modification of some data. The vulnerability also has potential to impact additional Oracle products beyond MES for Process Manufacturing. The CVSS 3.1 base score is 8.2, reflecting high confidentiality impact and limited integrity impact. | CVE Database V5 | ||
| High | 7.5 | EPSS 0.6% | CVE-2026-58436Gitea · ParseAcceptLanguage quadratic-time DoS via Locale middleware on unauthenticated requests High7.5EPSS 0.6%GCVE DatabaseCVE-2026-58436 ### Summary The Locale middleware that runs in front of every unauthenticated request calls `golang.org/x/text/language.ParseAcceptLanguage` on the raw `Accept-Language` header without imposing a size or shape filter. The underlying parser has quadratic-time behaviour on long lists of malformed language tags. The CVE-2022-32149 guard that golang.org/x/text added in v0.3.8 caps the number of `-` characters in the input at 1000, but it does not cap `_` characters even though the parser's internal scanner aliases `_` to `-` before parsing. A single unauthenticated GET request with an `Accept-Language` header built out of `_` separators burns ~2 seconds of server CPU on the host running Gitea; ten concurrent attackers saturate a ten-core box for the duration of the attack while consuming ~1 MiB of upstream bandwidth per request. ### Affected versions `code.gitea.io/gitea` 1.22.6 and (per code inspection of `main`) all earlier and later 1.22.x / 1.23.x / 1.24.x / 1.25.x / 1.26.x versions that do not impose their own size limit on the `Accept-Language` header before calling `ParseAcceptLanguage`. Verified on: - the official `gitea/gitea:1.22.6` docker image (E2E below) - `main` at commit `6f4027a6be28c876c0abaf37cc939658645b78a3` by reading `modules/web/middleware/locale.go` (the call site at line 38 is unchanged on `main`) ### Privilege required Unauthenticated. The Locale middleware runs for every HTTP request including the landing page and the sign-in page. ### Vulnerable code [`modules/web/middleware/locale.go:38`](https://github.com/go-gitea/gitea/blob/fc396f0808187c358b4fc15dcefcd6957140a780/modules/web/middleware/locale.go#L38) (blob SHA `fc396f0808187c358b4fc15dcefcd6957140a780`): ```go // 3. Get language information from 'Accept-Language'. // The first element in the list is chosen to be the default language automatically. if len(lang) == 0 { tags, _, _ := language.ParseAcceptLanguage(req.Header.Get("Accept-Language")) tag := translation.Match(tags...) lang = tag.String() } ``` `req.Header.Get("Accept-Language")` is the unfiltered HTTP header. Default Go `net/http` `MaxHeaderBytes` is `1 << 20` = 1 MiB and Gitea does not override it, so the parser is allowed to receive up to a megabyte of attacker-controlled data. CVE-2022-32149 hardened `ParseAcceptLanguage` by counting `-` characters and rejecting inputs with more than 1000 of them. The guard does not count `_` characters even though the scanner converts `_` to `-` at parse time ([`golang.org/x/text/internal/language/parse.go`](https://github.com/golang/text/blob/v0.28.0/internal/language/parse.go)). A 1 MiB header full of 9-character `_aaaaaaaaa_aaaaaaaaa_...` tokens contains zero `-` characters, passes the guard, and then drives the scanner into the O(N²) `gobble` path. The fix author of CVE-2022-32149 treated `-` as the canonical separator; the `_` alias was added in 2013, nine years before the fix. ### How `Accept-Language` reaches `ParseAcceptLanguage` Every Gitea HTTP request passes through `Locale` as it is wired up via the global request pipeline (Gitea registers the middleware on its router in `routers/web/web.go`). The middleware sequence is: 1. The request enters `Locale(resp, req)`. 2. `req.URL.Query().Get("lang")` returns "" (attacker omits `lang`). 3. `req.Cookie("lang")` returns nil on a fresh client (attacker uses a fresh client, or simply does not send the cookie). 4. `req.Header.Get("Accept-Language")` returns the full attacker-supplied header value. 5. `language.ParseAcceptLanguage(...)` runs unfiltered. No size or character class filter is applied between (4) and (5). ### Proof of concept Single-line bash reproducer that crafts the malicious header and times one request against a fresh `gitea/gitea:1.22.6` container: ```bash docker run -d --name gitea --rm -p 13000:3000 gitea/gitea:1.22.6 sleep 8 PAYLOAD="en$(python3 -c 'print("_abcdefghi" * 100000, end="")')" echo "header size = ${#PAYLOAD} bytes" curl -sS -o /dev/null \ -w 'http=%{http_code} t=%{time_total}\n' \ -H "Accept-Language: ${PAYLOAD}" \ http://127.0.0.1:13000/ ``` Each 9-character `_abcdefghi` token has length 9, which fails the scanner's `len <= 8` tag-length check at `golang.org/x/text/internal/language/parse.go` and triggers a `gobble` call that `runtime.memmove`s the entire remaining buffer. With N invalid tokens the total bytes moved by `gobble` is O(N²). ### End-to-end reproduction (against `gitea/gitea:1.22.6`) A Go driver `poc.go` that boots the container, sends a 1 MiB `Accept-Language` value once with `-` (CVE-2022-32149 guard fires) and once with `_` (guard bypassed): ```go // poc.go package main import ( "fmt" "io" "net" "net/http" "strings" "time" ) const targetURL = "http://127.0.0.1:13000/" func buildPayload(sep string, targetBytes int) string { const tok = "abcdefghi" var b strings.Builder b.Grow(targetBytes + 16) b.WriteString("en") for b.Len()+1+len(tok) < | GCVE Database | ||
| Medium | 5.4 | EPSS 0.3% | Medium5.4EPSS 0.3%GCVE DatabaseCVE-2026-58435 ## Vulnerability Header | Field | Value | | ------------------- | ----------------------------------------------------------- | | Vulnerability Title | Gitea LFS Deploy-Key Privilege Escalation | | Severity Rating | High | | Bug Category | Insufficient Authorization | | Location | `services/lfs/server.go:268`, `routers/private/serv.go:275` | | Affected Versions | 1.25.5 | ## Executive Summary Gitea's LFS server (`services/lfs/server.go:268`) uses the `UserID` embedded in an LFS JWT to make cross-repository authorization decisions via `LFSObjectAccessible()`. This would be safe if the JWT `UserID` always matched the actual requesting principal — but for deploy keys, `routers/private/serv.go:275` sets `UserID = repo.OwnerID` instead of any identity representing the deploy key itself. As a result, an attacker who holds a write deploy key for any single repo owned by a victim can obtain a legitimate JWT (via the standard SSH `git-lfs-authenticate` flow) that Gitea will honor as if the victim themselves were making the request. The attacker can then exfiltrate LFS objects from any private repo the victim owns — no admin credentials, no server secrets, no brute force required. If the victim is a site administrator, every LFS object on the entire Gitea instance is reachable. Deploy keys exist precisely to grant narrow, single-repo access to CI/CD systems; this vulnerability defeats that isolation entirely for LFS data. ## Root Cause Analysis ### Technical Description The vulnerability is a **trust-boundary confusion** across two independent subsystems. When a deploy key authenticates over SSH, `serv.go` sets `UserID = repo.OwnerID` because the code has no better representation for a deploy key identity (a `FIXME` comment acknowledges this). That `UserID` is baked verbatim into the LFS JWT by `cmd/serv.go`. The JWT is then consumed by `server.go`, which treats `claims.UserID` as the authenticated principal and loads that user object as `ctx.Doer`. When the batch upload handler encounters an object that exists on disk but isn't yet linked to the target repo, it calls `LFSObjectAccessible(ctx, ctx.Doer, oid)` — a global query across all repos the claimed user can see — to decide whether to silently create the cross-repo link. The JWT's `RepoID` claim is verified (so the request is correctly scoped to one repo at the HTTP level), but the `UserID` driving the cross-repo access decision is the repo *owner*, not the deploy key. The attacker ends up holding a valid, server-signed token that impersonates the victim for any LFS authorization check. ### First Faulty Condition The primary bug — where the JWT `UserID` is set incorrectly — is in `serv.go`: | File | `routers/private/serv.go` | | --------- | ------------------------------------------------------------------------------------------------- | | Line | 275 | | Condition | Deploy key branch sets `results.UserID = repo.OwnerID`; the owner's UID is embedded in the JWT and later used as the authenticated principal for cross-repo privilege decisions in `server.go:268` | ```go // routers/private/serv.go:252–278 if key.Type == asymkey_model.KeyTypeDeploy { ... // FIXME: Deploy keys aren't really the owner of the repo pushing changes // however we don't have good way of representing deploy keys in hook.go // so for now use the owner of the repository results.UserName = results.OwnerName results.UserID = repo.OwnerID // ← OWNER's UID, not the deploy key ... } ``` The secondary bug — where the tainted `UserID` is actually misused — is in `server.go`: | File | `services/lfs/server.go` | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Line | 268 | | Condition | `LFSObjectAccessible(ctx, ctx.Doer, oid)` makes a cross-repo decision using the JWT `UserID`, which for deploy keys is the repo owner, not the deploy key holder | ```go // services/lfs/server.go:267–275 if exists && meta == nil { accessible, err := git_model.LFSObjectAccessible(ctx, ctx.Doer, p.Oid) ... if accessible { _, err := git_model.NewLFSMetaObject(ctx, repository.ID, p) // links OID to attacker's repo | GCVE Database | ||
| Low | 2.7 | EPSS 0.4% | CVE-2026-58445Gitea · Cross-repository label-ID enumeration oracle via unscoped DeleteIssueLabel API Low2.7EPSS 0.4%GCVE DatabaseCVE-2026-58445 ## Summary The API endpoint `DELETE /repos/{owner}/{repo}/issues/{index}/labels/{id}` loads the label by ID with a **global, unscoped** lookup and never verifies the label belongs to the URL's repository (or its owning organization). Because the response status differs by whether the label ID exists **anywhere on the instance** (204) versus not (422), an authenticated user can use the endpoint as a **cross-repository label-ID existence / enumeration oracle**, including for labels in repositories and organizations they cannot access. ## Severity - The leaked information is minimal (existence/count of label IDs instance-wide); **no label name, color, or owning repository is disclosed, and no cross-repository write occurs.** ## Affected / patched versions - **Affected:** through **1.26.3** (latest at time of report). - **Patched:** none yet. ## Details `DeleteIssueLabel` resolves the label with a global loader and never checks its scope: ```go // routers/api/v1/repo/issue_label.go (DeleteIssueLabel) label, err := issues_model.GetLabelByID(ctx, ctx.PathParamInt64("id")) // global, unscoped ``` `GetLabelByID` (`models/issues/label.go`) is `e.ID(labelID).Get(l)` with **no** `repo_id` / `org_id` filter. The handler never verifies `label.RepoID == ctx.Repo.Repository.ID` (nor the org-label equivalent), and the downstream `issue_service.RemoveLabel` (`services/issue/label.go`) only re-checks the doer's write permission on the **issue's own** repository — never that the label belongs to it. Every sibling label handler is correctly scoped — `GetLabel` / `EditLabel` / `DeleteLabel` (repo and org) use `GetLabelInRepoByID` / `GetLabelInOrgByID` and return 404 for a foreign ID. `DeleteIssueLabel` is the only outlier. **Why it is only an oracle:** `deleteIssueLabel` (`models/issues/issue_label.go`) deletes the `issue_label` row keyed by `(issue.ID, label.ID)`. For a foreign label, no such row exists → the function returns early **before** any mutation or comment creation. So there is no cross-repo write and no leak of the label's name. But the HTTP status differs: - label ID exists anywhere on the instance (incl. private repos/orgs) → **204 No Content** - label ID does not exist → **422** (`ErrLabelNotExist`) Label IDs are sequential auto-increment, so this enumerates the instance-wide label population and probes existence of specific IDs across tenant boundaries. ## Proof of Concept Verified end-to-end on a build of the `v1.26.3` tag. - `alice` (private repo `alice/secret`) creates a label → internal id **1**. - Attacker `bob` (separate user; public repo `bob/pub` with issue #1; **no access** to `alice/secret`) holds a token with `write:issue` on his own repo. ```text bob DELETE /api/v1/repos/bob/pub/issues/1/labels/1 -> HTTP 204 (alice's PRIVATE label id exists) bob DELETE /api/v1/repos/bob/pub/issues/1/labels/99999999 -> HTTP 422 (no such label) ``` Differing only by the label ID: `204` vs `422` distinguishes "label ID exists" from "does not exist." `bob` has zero rights to `alice/secret` but can still learn label id 1 exists. (Alice's label is untouched — no write.) Reproduction steps: 1. Create two users `alice`, `bob`. As `alice`, create a private repo and a label on it (note the label `id` from the API response). 2. As `bob`, create any repo with an issue, and a token with `write:issue`. 3. `curl -u bob:$T -X DELETE https://<gitea>/api/v1/repos/bob/pub/issues/1/labels/<alice_label_id>` → **204**. 4. `curl -u bob:$T -X DELETE https://<gitea>/api/v1/repos/bob/pub/issues/1/labels/99999999` → **422**. 5. The differing status across an ID `bob` cannot otherwise see is the oracle. ## Impact Cross-tenant authorization-key bypass producing a label-ID existence/enumeration oracle: an authenticated user can determine whether arbitrary label IDs (including in private repositories and organizations they cannot access) exist, and enumerate the instance-wide label population. ## Remediation Scope the loader like every sibling handler, returning 404 for a label that is not in the URL repository (or its owning organization), so the status no longer distinguishes existence: ```go // routers/api/v1/repo/issue_label.go — in DeleteIssueLabel, after loading the label if label.RepoID != ctx.Repo.Repository.ID && label.OrgID != ctx.Repo.Repository.OwnerID { ctx.APIErrorNotFound() return } ``` (Equivalently, resolve via `GetLabelInRepoByID` and, for org repositories, also accept the repo owner's org labels — mirroring the scoping in `GetLabel`/`EditLabel`/`DeleteLabel`.) | GCVE Database | ||
| Medium | 6.5 | EPSS 0.4% | Medium6.5EPSS 0.4%GCVE DatabaseCVE-2026-58442 ### Summary Gitea's repository migration URL validation can be bypassed when a migration hostname resolves to multiple IP addresses. The validation logic accepts the destination if **any** resolved IP is allowed, even if another resolved IP is loopback, private, or otherwise blocked. The later `git clone` operation resolves the hostname again outside of that validation decision, so it can connect to the internal address. An authenticated low-privilege user who can create repository migrations can use an attacker-controlled DNS name to make Gitea connect to internal-only Git services and import their contents into a repository controlled by the attacker. ### Details The issue is in `services/migrations/migrate.go`, in the migration allow/block-list check. Current logic computes whether any resolved IP is allowed: ```go var ipAllowed bool var ipBlocked bool for _, addr := range addrList { ipAllowed = ipAllowed || allowList.MatchIPAddr(addr) ipBlocked = ipBlocked || blockList.MatchIPAddr(addr) } ``` Then, when an allow-list is active, the host is accepted if the hostname matches or `ipAllowed` is true: ```go if !allowList.IsEmpty() { if !allowList.MatchHostName(hostName) && !ipAllowed { return &git.ErrInvalidCloneAddr{Host: hostName, IsPermissionDenied: true} } } ``` This means a hostname resolving to both: - an allowed public IP, e.g. `1.2.3.4` - a blocked internal IP, e.g. `127.0.0.1` passes validation because the public IP sets `ipAllowed = true`. The actual repository import is later performed by `git clone --mirror` via `MigrateRepositoryGitData` / `gitrepo.CloneExternalRepo`. That git subprocess performs its own DNS resolution and is not tied to the specific IP set that was validated earlier. If the hostname resolves, rotates, or is re-bound to the internal address at clone time, Gitea can connect to a destination the migration filter would reject if supplied directly. The direct internal URL is correctly blocked, but the multi-answer hostname is accepted. ### PoC I verified the vulnerable predicate locally against Gitea checkout: ```text e8654c7e062431a521636703f47339cde64644fd ``` using Dockerized Go tests with `golang:1.26.4`. The local test proves: - `checkByAllowBlockList("loopback.example.test", [127.0.0.1])` is rejected. - `checkByAllowBlockList("mixed.example.test", [1.2.3.4, 127.0.0.1])` is accepted. Minimal reproducer at the validation layer: ```go func TestMigrationMultiAnswerAnyAllowed(t *testing.T) { old := setting.Migrations t.Cleanup(func() { setting.Migrations = old }) setting.Migrations.AllowedDomains = "" setting.Migrations.BlockedDomains = "" setting.Migrations.AllowLocalNetworks = false require.NoError(t, Init()) err := checkByAllowBlockList("mixed.example.test", []net.IP{ net.ParseIP("1.2.3.4"), net.ParseIP("127.0.0.1"), }) require.NoError(t, err, "mixed public+loopback answers should currently pass") err = checkByAllowBlockList("loopback.example.test", []net.IP{ net.ParseIP("127.0.0.1"), }) require.Error(t, err, "loopback-only answer should be rejected") } ``` To reproduce end-to-end: 1. Run Gitea with repository migration enabled and `ALLOW_LOCALNETWORKS = false`. 2. Create a normal non-admin user that can create repositories. 3. Run an internal Git HTTP service reachable only from the Gitea server, for example on `127.0.0.1:18082`. 4. Configure an attacker-controlled hostname so that DNS can return both a public address and `127.0.0.1`, or can return a public address during Gitea's pre-flight validation and `127.0.0.1` during the later git clone. 5. Confirm the direct internal migration is rejected: ```bash curl -X POST http://GITEA/api/v1/repos/migrate \ -H "Authorization: token USER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "clone_addr": "http://127.0.0.1:18082/repo.git", "repo_name": "direct-internal", "service": "git", "private": true }' ``` Expected direct result: ```json {"message":"You can not import from disallowed hosts."} ``` 6. Start a migration from the attacker-controlled multi-answer hostname: ```bash curl -X POST http://GITEA/api/v1/repos/migrate \ -H "Authorization: token USER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "clone_addr": "http://mixed.example.test:18082/repo.git", "repo_name": "multidns-ssrf", "service": "git", "private": true }' ``` Expected vulnerable result: - The migration request is accepted. - The git subprocess can connect to the internal address. - Internal repository contents are imported into the attacker's new Gitea repository. ### Impact This is a server-side request forgery in repository migration. An authenticated user with permission to create repository migrations can make the Gitea server connect to internal-only network resources that are normally blocked by the migration SSRF filter. If the internal service is a Git repository or Git-com | GCVE Database | ||
| Medium | 6.3 | EPSS 0.2% | Medium6.3EPSS 0.2%GCVE DatabaseCVE-2026-58441 ### Summary Gitea's `restore-repo` CLI command restores a repository from a dump directory/archive. When parsing `pull_request.yml` from that dump, the `Head.CloneURL` field is used to add a git remote and fetch from it with no validation, because the safety check that's supposed to guard it (`CheckAndEnsureSafePR`) is called with an empty `commonCloneBaseURL`, which silently disables it. This lets a malicious dump make the Gitea server execute `git fetch` against an attacker-chosen URL (SSRF), or disclose a local git repository via `file://`. This is a different root cause from the recently fixed path-traversal issue in the same command (#38215), which patched `DownloadURL`/`PatchURL` but not `Head.CloneURL`. ### Details `services/migrations/restore.go`'s `GetPullRequests()` unmarshals `pull_request.yml` directly into `base.PullRequest` structs with no validation of `Head.CloneURL`: ```go err = yaml.Unmarshal(bs, &pulls) ... for _, pr := range pulls { if pr.PatchURL != "" { pr.PatchURL = "file://" + util.FilePathJoinAbs(r.baseDir, pr.PatchURL) } CheckAndEnsureSafePR(pr, "", r) // <-- empty baseURL } ``` `CheckAndEnsureSafePR` (`services/migrations/common.go`) is supposed to reject `Head.CloneURL`/`PatchURL` values that don't share a common base URL: ```go func hasBaseURL(toCheck, baseURL string) bool { if len(baseURL) > 0 && baseURL[len(baseURL)-1] != '/' { baseURL += "/" } return strings.HasPrefix(toCheck, baseURL) } func CheckAndEnsureSafePR(pr *base.PullRequest, commonCloneBaseURL string, g base.Downloader) bool { valid := true if pr.PatchURL != "" && !hasBaseURL(pr.PatchURL, commonCloneBaseURL) { pr.PatchURL = "" valid = false } if pr.Head.CloneURL != "" && !hasBaseURL(pr.Head.CloneURL, commonCloneBaseURL) { pr.Head.CloneURL = "" valid = false } return valid } ``` `strings.HasPrefix(anything, "")` is always `true` in Go. Because `restore.go` is the only caller that passes `""` as `commonCloneBaseURL`, this check is a complete no-op on the restore-repo path — `Head.CloneURL` survives unchanged regardless of its value. Every other downloader (`github.go`, `gitlab.go`, `gitea_downloader.go`, `codebase.go`, `codecommit.go`, `onedev.go`) passes a real base URL, so they are not affected. `services/migrations/gitea_uploader.go` then uses the unvalidated value directly: ```go err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true) // ... later: fetch from that remote ``` resulting in the server executing `git fetch` against an attacker-controlled URL sourced from the dump file. **RCE via git's `ext::` transport helper was tested and ruled out** — a normal `git` install rejects it by default (`fatal: transport 'ext' not allowed`), independent of Gitea's own configuration. This report is scoped to SSRF and local git-repository disclosure. Confirmed present, byte-for-byte identical, in `v1.26.4` (latest stable tag), `release/v1.27`, and `main`, by direct checkout and diff. ### PoC 1. Create a dump directory following the normal `restore-repo` layout (`repo.yml`, etc.), and add a `pull_request.yml` containing at least one entry with: ```yaml - number: 1 head: cloneURL: "http://<attacker-controlled-or-internal-host>:<port>/ssrf-proof" ref: "main" ``` 2. Run `gitea restore-repo` against that dump directory for any repo owner. 3. Observe on the target host/listener: an actual `git` HTTP discovery request arrives, e.g. `GET /ssrf-proof/info/refs?service=git-upload-pack`, driven entirely by the value from the dump file. Verified the core mechanism (steps 2–3, i.e. the unvalidated `Head.CloneURL` surviving `CheckAndEnsureSafePR("")` and then being used in a real `git remote add` + `git fetch`) with a minimal, standalone Go program built from the **verbatim, unmodified** `hasBaseURL` / `CheckAndEnsureSafePR` function bodies (attached: `gitea_ssrf_poc.go`), run end-to-end against a local HTTP listener. The listener's access log confirms the request actually arrives. ### Impact An attacker who can get an administrator to run `gitea restore-repo` against a malicious dump (the same threat model already accepted for the just-fixed path-traversal issue in this command, #38215) can make the Gitea server issue a `git fetch` against an arbitrary attacker-chosen URL. This allows: - SSRF against internal-only services or cloud metadata endpoints reachable from the Gitea host. - Disclosure of local git repositories reachable via `file://` paths readable by the Gitea process. No public disclosure planned. Happy to provide further detail on request. | GCVE Database | ||
| High | 7.5 | EPSS 0.5% | CVE-2026-58438gitea.dev · Gitea: Cross-repository IDOR High7.5EPSS 0.5%GCVE DatabaseCVE-2026-58438 ### Details `RemoveDependency` in `routers/web/repo/issue_dependency.go` takes a `removeDependencyID` form parameter identifying the other issue by its global numeric ID, and fetches it with `issues_model.GetIssueByID(ctx, depID)` - no repository or permission check at all. It then calls `issues_model.RemoveIssueDependency(ctx, ctx.Doer, issue, dep, depType)` (`models/issues/dependency.go`), which deletes the dependency join row and then writes a comment referencing the removal, attributed to the calling user, onto the dependency record. The sibling function in the very same file, `AddDependency`, does this correctly when the two issues are in different repos (which `ALLOW_CROSS_REPOSITORY_DEPENDENCIES`, on by default, permits): ```go if issue.RepoID != dep.RepoID { if !setting.Service.AllowCrossRepositoryDependencies { ... } depRepoPerm, err := access_model.GetDoerRepoPermission(ctx, dep.Repo, ctx.Doer) if !depRepoPerm.CanReadIssuesOrPulls(dep.IsPull) { return // you can't see this dependency } } ``` `RemoveDependency` has no equivalent block at all - it goes straight from resolving `dep` by ID to deleting the link, regardless of which repo `dep` lives in or whether the caller can see it. I confirmed this same code is present in the current latest release, v1.26.4. ### PoC Prerequisites: an account with write access to issues on some repo `ownerA/repoA`, and the global numeric issue ID of an issue in a private repo `repoB` that is (or was) legitimately dependency-linked to one of the attacker's issues in `repoA` (cross-repo dependencies are commonly used between related public/private repos, and `ALLOW_CROSS_REPOSITORY_DEPENDENCIES` defaults to enabled). ```bash curl -s -b "gitea_session=$ATTACKER_SESSION_COOKIE" -X POST \ --data-urlencode "removeDependencyID=<repoB_issue_global_id>" \ --data-urlencode "dependencyType=blockedBy" \ "https://TARGET_HOST/ownerA/repoA/issues/N/dependency/delete" # Expected: the dependency link is deleted and a "removed dependency" comment # authored by the attacker is added to the repoB issue, even though the # attacker has no read access to repoB. ``` ### Impact This is a cross-repository IDOR / broken access control issue. An attacker can tamper with issue-tracking state (dependency relationships) and inject an attacker-authored comment into a private repository they cannot otherwise read or write to, crossing a trust boundary the "add" path explicitly enforces. Impact is bounded - it requires an existing dependency link and discloses no repository content - but it is a genuine unauthorized-write primitive across a private-repo boundary. ### Fix Add the same cross-repo permission check used in `AddDependency` (`access_model.GetDoerRepoPermission(ctx, dep.Repo, ctx.Doer).CanReadIssuesOrPulls(dep.IsPull)`) to `RemoveDependency` before allowing the deletion to proceed when `issue.RepoID != dep.RepoID`. **If possible, please apply for a CVE number when publishing. I would greatly appreciate it.** | GCVE Database | ||
| Critical | 9.8 | EPSS 0.8% | Critical9.8EPSS 0.8%GCVE DatabaseCVE-2026-56654 Gitea's API endpoint for creating Personal Access Tokens (`POST /users/{username}/tokens`) is protected by a middleware (`reqBasicOrRevProxyAuth`) that is intended to require password-based authentication, preventing a compromised token from being used to mint new ones. However, when a token is passed in the `Authorization: Basic <token>:x-oauth-basic` format, the Basic auth handler validates it and sets `AuthedMethod="basic"`, causing `IsBasicAuth=true` and fooling the middleware into passing the request. Once past the guard, the token creation handler applies no scope ceiling — it will create a new token with any requested scope regardless of the caller's scope. An attacker with a restricted token (e.g. `write:user` from a leaked CI secret) can therefore create a fully-privileged `all`-scoped token without knowing the account password. ### Data flow #### Step 1 - Token extracted from Basic auth header When the attacker sends Authorization: Basic base64(<token>:x-oauth-basic), parseAuthBasic detects that the password is "x-oauth-basic" and treats the username field as the token: https://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/services/auth/basic.go#L55-L64 `VerifyAuthToken` then validates the token against the database and sets `LoginMethod = "access_token"` and `ApiTokenScope` to the token's actual scope (`write:user`): https://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/services/auth/basic.go#L100-L106 #### Step 2 - `AuthedMethod` is set to `"basic"`, not `"access_token"` `Basic.Verify()` returns the user successfully, so `group.Verify()` sets `AuthedMethod` to the method's name — `"basic"` — regardless of whether a password or token was used: https://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/services/auth/group.go#L63-L65 #### Step 3 - `IsBasicAuth` is incorrectly set to true `AuthShared` computes `IsBasicAuth` by comparing `AuthedMethod` against the constant `"basic"`. Since step 2 set that field to "basic" for a token-authenticated request, the flag is wrong: https://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/routers/common/auth.go#L27 #### Step 4 - The guard is bypassed `reqBasicOrRevProxyAuth` checks only `ctx.IsBasicAuth`. Because that flag is `true`, the middleware passes and the request reaches `CreateAccessToken`: https://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/routers/api/v1/api.go#L392-L401 #### Step 5 - No scope ceiling in the handler `CreateAccessToken` normalizes the caller-supplied scope and assigns it directly to the new token. There is no check that the requested scope is a subset of `ApiTokenScope (write:user)`: https://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/routers/api/v1/user/app.go#L119-L128 ### Reproducing `tests/integration/api_token_scope_escalation_test.go` ```go package integration import ( "net/http" "testing" auth_model "gitea.dev/models/auth" "gitea.dev/models/unittest" user_model "gitea.dev/models/user" api "gitea.dev/modules/structs" "gitea.dev/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // TestAPIPrivilegeEscalationViaBasicAuthToken is a proof-of-concept for two // interconnected vulnerabilities that together allow full scope escalation: // // 1. reqBasicOrRevProxyAuth() is fooled into passing when a PAT is supplied in // the Authorization: Basic "<token>:x-oauth-basic" format. The Basic auth // handler sets AuthedMethod="basic" (the method name), so IsBasicAuth=true // even though the credential is a token, not a password. // // 2. CreateAccessToken performs no scope-ceiling check — it never verifies that // the requested scopes are a subset of the caller's token scopes. // // Combined effect: an attacker with a write:user-scoped token can create a new // token with the "all" scope, gaining full access to the account. func TestAPIPrivilegeEscalationViaBasicAuthToken(t *testing.T) { defer tests.PrepareTestEnv(t)() // Non-admin user — escalation is meaningful and not trivially justified. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Step 1 — Obtain a legitimately restricted token via password-based Basic auth. // Only write:user scope is granted; repository, admin, etc. are excluded. restrictedToken := createAPIAccessTokenWithoutCleanUp(t, "poc-restricted", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteUser}) defer deleteAPIAccessToken(t, restrictedToken, user) // Confirm the restricted token is blocked from repository-scoped endpoints. // This establishes the baseline: write:user does not imply read:repository. req := NewRequest(t, "GET", "/api/v1/repos/search"). AddTokenAuth(restrictedToken.Token) MakeRequest(t, req, http.StatusForbidden) // Step 2 — Exploit: supply the restricted token as Basic auth credentials. // Authorization: B | GCVE Database |
Page 33 of 100