---
id: "CVE-2026-113729"
kind: "vulnerability"
aliases: ["CVE-2026-113729", "GHSA-5588-rp99-5fjh"]
title: "Moov signedxml: Unauthenticated denial of service in exclusive XML canonicalization"
severity: "high"
cvss: 7.5
epss: 0.004650000017136335
exploited: false
status: "open"
category: "package"
products: [{"name": "github.com/moov-io/signedxml", "fixed": ["1.4.1"], "affected": "<= 1.4.0", "ecosystem": "go"}]
ecosystems: ["go"]
tags: ["cwe-400", "cwe-407", "cwe-770", "go", "package", "saml", "ws-federation", "xml-signature"]
not_affected_if: ["no-xml-signature-parsing", "version-1.4.1-or-later"]
published_at: "2026-10-20T22:19:29+00:00"
first_seen_at: "2026-10-23T19:11:32+00:00"
updated_at: "2026-10-23T21:57:25+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-5588-rp99-5fjh", "first_seen_at": "2026-10-23T19:11:32+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-113729", "first_seen_at": "2026-10-23T21:57:25+00:00"}]
---

# Moov signedxml: Unauthenticated denial of service in exclusive XML canonicalization

The Go library github.com/moov-io/signedxml copies the full namespace map at every node during exclusive XML canonicalization, before any signature check. A small unauthenticated XML document with deeply nested elements and many namespace prefixes can drive multi-gigabyte memory use and crash the process.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- go:github.com/moov-io/signedxml affected <= 1.4.0 fixed 1.4.1

## How to check

Run go list -m github.com/moov-io/signedxml and check whether the version is 1.4.0 or lower.
Check whether the service parses attacker-supplied XML with this library, such as SAML or WS-Federation endpoints.

## Fix

Upgrade github.com/moov-io/signedxml to 1.4.1 or later.
If upgrading is not possible, unknown.

## Timeline

- 2026-10-23T19:11:32+00:00 ghsa https://github.com/advisories/GHSA-5588-rp99-5fjh
- 2026-10-23T21:57:25+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-113729

## From the sources

Exclusive XML canonicalization (`http://www.w3.org/2001/10/xml-exc-c14n#`) in `github.com/moov-io/signedxml` copied the entire accumulated namespace map at every node during `ValidateReferences()`, before any reference digest or signature check.

For a chain of nested elements that each declare many unused namespace prefixes, that produced **O(m × depth²)** time and peak memory (`m` = prefixes per level). `beevik/etree` caps tree depth (~1024), but `m` is unbounded, so a small attacker-controlled document (on the order of 1 MB) could drive multi-gigabyte allocations and OOM the process. No valid signature, matching certificate, or authentication is required.

This is the library’s core path for SAML / WS-Federation service providers, which canonicalize attacker-supplied XML prior to authentication.

### Affected

- Package: `github.com/moov-io/signedxml`
- Function: `ExclusiveCanonicalization` (`exclusivecanonicalization.go`)
- Verified: **v1.4.0** and current default branch (commit `d79064a`). Earlier v1.2.x–v1.3.x releases are likely affected.
 

### Remediation

Do not copy the whole namespace map per node. Maintain a single scope and push/pop only the prefixes introduced at each element. Replace linear prefix-in-scope scans with set lookups. Impose a defensive cap on in-scope namespace declarations and return an error rather than allocating unbounded memory.

From @shuji-oh 
```
Thanks for the fix — I re-ran my PoC against this branch and confirmed the memory blowup is gone.

Before (main)

-- Doubling depth each time (m=10 fixed): allocated grows ~4x each time ≈ O(depth^2) --
depth=250  m=10  payload=  0.07MB allocated=     45MB
depth=500  m=10  payload=  0.14MB allocated=    179MB
depth=1000 m=10  payload=  0.27MB allocated=    711MB
-- Increasing m (depth=1000 fixed): allocated scales roughly linearly with m --
depth=1000 m=5   payload=  0.16MB allocated=    377MB
depth=1000 m=100 payload=  2.57MB allocated=   6278MB
After (fix/exc-c14n-namespace-dos)

-- Doubling depth each time (m=10 fixed): allocated grows ~4x each time ≈ O(depth^2) --
depth=250  m=10  payload=  0.07MB allocated=      2MB
depth=500  m=10  payload=  0.14MB allocated=      4MB
depth=1000 m=10  payload=  0.27MB allocated=      7MB
-- Increasing m (depth=1000 fixed): allocated scales roughly linearly with m --
depth=1000 m=5   payload=  0.16MB allocated=      4MB
depth=1000 m=100 payload=  2.57MB allocated=     47MB
That's roughly a 100–130x reduction in peak allocation for the same payload sizes, and no OOM in any of my runs. This looks like it resolves the issue — thanks for the quick fix!
```

### Credit

Reported by Shuji Ohira ([@shuji-oh](https://github.com/shuji-oh)).

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
