CVE-2026-100836
Edgeless Systems Contrast through 1.20.0 Denial of Service via ciphertextContainer
Contrast through 1.20.0 contains a panic vulnerability in the transit-engine endpoint's ciphertextContainer.UnmarshalJSON function that fails to validate decoded ciphertext length before slicing. An authenticated workload with a valid mesh certificate can trigger a runtime panic by submitting a short base64-encoded ciphertext, causing log spam and request failures without crashing the process.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
5.3
Edgeless Systems Contrast through 1.20.0 Denial of Service via ciphertextContainer
Contrast through 1.20.0 contains a panic vulnerability in the transit-engine endpoint's ciphertextContainer.UnmarshalJSON function that fails to validate decoded ciphertext length before slicing. An authenticated workload with a valid mesh certificate can trigger a runtime panic by submitting a short base64-encoded ciphertext, causing log spam and request failures without crashing the process.
- Record
CVE-2026-100836- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N - CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-129
- Updated
- 2026-11-02
EUVDEUVD-2026-87831
5.3
Contrast through 1.20.0 contains a panic vulnerability in the transit-engine endpoint's ciphertextContainer.UnmarshalJSON function that fails to validate decoded ciphertext length before slicing. An authenticated workload with a valid mesh certificate can trigger a runtime panic by submitting a short base64-encoded ciphertext, causing log spam and request failures without crashing the process.
- Record
EUVD-2026-87831- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-3ccm-4qq2-5wrp
4.3
Constrata's coordinator transit engine `ciphertextContainer.UnmarshalJSON` panics on attacker-controlled short ciphertexts
Summary
ciphertextContainer.UnmarshalJSON decodes the third :-separated component of a vault:vX:base64... ciphertext and then unconditionally takes a 12-byte prefix slice for the AES-GCM nonce: c.nonce = fullCiphertext[:aesGCMNonceSize]. If the decoded blob is shorter than 12 bytes, the slice expression panics. The panic happens before any cryptographic operation, while the JSON body of the request is still being parsed inside the request handler. Because the handler is invoked from net/http's standard handler goroutine, the panic is recovered to a 500 response, but the request handler aborts mid-execution and the recovered panic appears in the Coordinator's logs. An authenticated workload that holds a valid mesh certificate for any WorkloadSecretID can trigger the panic at will, producing log spam, request-failure metrics, and a slow but cheap denial of service against the transit-engine endpoint.
Details
the panicking slice
coordinator/internal/transitengineapi/crypto.go:64-88:
// UnmarshalJSON umarshalls a json string to a ciphertextContainer holding the version prefix,
// decoded base64 nonce and ciphertext.
func (c *ciphertextContainer) UnmarshalJSON(data []byte) error {
var encoded string
if err := json.Unmarshal(data, &encoded); err != nil {
return err
}
// Split "vault:vX:base64" format
parts := strings.SplitN(encoded, ":", 3)
if len(parts) < 3 {
return fmt.Errorf("invalid ciphertext format")
}
version, err := extractVersion(parts[1])
if err != nil {
return fmt.Errorf("ciphertext version: %w", err)
}
c.keyVersion = version
fullCiphertext, err := base64.StdEncoding.DecodeString(parts[2])
if err != nil {
return fmt.Errorf("decoding ciphertext: %w", err)
}
c.nonce = fullCiphertext[:aesGCMNonceSize] // PANIC when len(fullCiphertext) < 12
c.ciphertext = fullCiphertext[aesGCMNonceSize:]
return nil
}
aesGCMNonceSize = 12 (defined at line 33). There is no length check on fullCiphertext. If parts[2] decodes to fewer than 12 bytes (which happens for any base64 string shorter than ~16 characters), the slice expression fullCiphertext[:aesGCMNonceSize] triggers Go's runtime panic runtime error: slice bounds out of range [:12] with length N.
UnmarshalJSON is reached from parseRequest:
// coordinator/internal/transitengineapi/transitengineapi.go:292-302
func parseRequest(r *http.Request, into any) error {
defer r.Body.Close()
if err := validateContentType(r); err != nil {
return err
}
if err := json.NewDecoder(r.Body).Decode(into); err != nil {
return err
}
return nil
}
which is called inside getDecryptHandler (line 178-237) before any other processing.
auth requirement is real but trivial to satisfy for any registered workload
The transit-engine HTTP server (transitengineapi.go:74-100) configures tls.RequireAndVerifyClientCert with the Coordinator's mesh CA pool. The handler is wrapped by authorizationMiddleware (line 348-357) which calls authorizeWorkloadSecret (line 241-254). That function reads the WorkloadSecretOID extension from the peer cert and requires it to match the URL path's {name} segment.
Any workload that has gone through the normal initializer / meshapi flow (coordinator/internal/meshapi/meshapi.go:71-119) and has a non-empty WorkloadSecretID in its PolicyEntry is issued a mesh cert with the matching extension, so the path-name authorisation is automatically satisfied for whichever workloadSecretID the manifest assigned to that workload. There is no rate limiting, no proof-of-work, and no audit log on triggering the panic.
what happens after the panic
net/http wraps each handler in a recovered goroutine, so the panic does not crash the Coordinator process. Instead:
- The Go runtime captures the panic, logs
http: panic serving <peer>: runtime error: slice bounds out of rangeto stderr together with a goroutine stack trace. - The connection is hung up without a response…
- Record
GHSA-3ccm-4qq2-5wrp- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-129
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-x66w-59x9-vvcf
5.3
Contrast through 1.20.0 contains a panic vulnerability in the transit-engine endpoint's...
Contrast through 1.20.0 contains a panic vulnerability in the transit-engine endpoint's ciphertextContainer.UnmarshalJSON function that fails to validate decoded ciphertext length before slicing. An authenticated workload with a valid mesh certificate can trigger a runtime panic by submitting a short base64-encoded ciphertext, causing log spam and request failures without crashing the process.
- Record
GHSA-x66w-59x9-vvcf- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-129
- Updated
- 2026-11-02
NVD
5.3
Contrast through 1.20.0 contains a panic vulnerability in the transit-engine endpoint's ciphertextContainer.UnmarshalJSON function that fails to validate decoded ciphertext length before slicing. An authenticated workload with a valid mesh certificate can trigger a runtime panic by submitting a short base64-encoded ciphertext, causing log spam and request failures without crashing the process.
- Record
CVE-2026-100836- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-129
- Updated
- 2026-11-02
OSV
5.3
Edgeless Systems Contrast through 1.20.0 Denial of Service via ciphertextContainer
Contrast through 1.20.0 contains a panic vulnerability in the transit-engine endpoint's ciphertextContainer.UnmarshalJSON function that fails to validate decoded ciphertext length before slicing. An authenticated workload with a valid mesh certificate can trigger a runtime panic by submitting a short base64-encoded ciphertext, causing log spam and request failures without crashing the process.
- Record
CVE-2026-100836- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N - Weakness
- CWE-129
- Updated
- 2026-11-02
OSVGHSA-3ccm-4qq2-5wrp
4.3
Constrata's coordinator transit engine `ciphertextContainer.UnmarshalJSON` panics on attacker-controlled short ciphertexts
Summary
ciphertextContainer.UnmarshalJSON decodes the third :-separated component of a vault:vX:base64... ciphertext and then unconditionally takes a 12-byte prefix slice for the AES-GCM nonce: c.nonce = fullCiphertext[:aesGCMNonceSize]. If the decoded blob is shorter than 12 bytes, the slice expression panics. The panic happens before any cryptographic operation, while the JSON body of the request is still being parsed inside the request handler. Because the handler is invoked from net/http's standard handler goroutine, the panic is recovered to a 500 response, but the request handler aborts mid-execution and the recovered panic appears in the Coordinator's logs. An authenticated workload that holds a valid mesh certificate for any WorkloadSecretID can trigger the panic at will, producing log spam, request-failure metrics, and a slow but cheap denial of service against the transit-engine endpoint.
Details
the panicking slice
coordinator/internal/transitengineapi/crypto.go:64-88:
// UnmarshalJSON umarshalls a json string to a ciphertextContainer holding the version prefix,
// decoded base64 nonce and ciphertext.
func (c *ciphertextContainer) UnmarshalJSON(data []byte) error {
var encoded string
if err := json.Unmarshal(data, &encoded); err != nil {
return err
}
// Split "vault:vX:base64" format
parts := strings.SplitN(encoded, ":", 3)
if len(parts) < 3 {
return fmt.Errorf("invalid ciphertext format")
}
version, err := extractVersion(parts[1])
if err != nil {
return fmt.Errorf("ciphertext version: %w", err)
}
c.keyVersion = version
fullCiphertext, err := base64.StdEncoding.DecodeString(parts[2])
if err != nil {
return fmt.Errorf("decoding ciphertext: %w", err)
}
c.nonce = fullCiphertext[:aesGCMNonceSize] // PANIC when len(fullCiphertext) < 12
c.ciphertext = fullCiphertext[aesGCMNonceSize:]
return nil
}
aesGCMNonceSize = 12 (defined at line 33). There is no length check on fullCiphertext. If parts[2] decodes to fewer than 12 bytes (which happens for any base64 string shorter than ~16 characters), the slice expression fullCiphertext[:aesGCMNonceSize] triggers Go's runtime panic runtime error: slice bounds out of range [:12] with length N.
UnmarshalJSON is reached from parseRequest:
// coordinator/internal/transitengineapi/transitengineapi.go:292-302
func parseRequest(r *http.Request, into any) error {
defer r.Body.Close()
if err := validateContentType(r); err != nil {
return err
}
if err := json.NewDecoder(r.Body).Decode(into); err != nil {
return err
}
return nil
}
which is called inside getDecryptHandler (line 178-237) before any other processing.
auth requirement is real but trivial to satisfy for any registered workload
The transit-engine HTTP server (transitengineapi.go:74-100) configures tls.RequireAndVerifyClientCert with the Coordinator's mesh CA pool. The handler is wrapped by authorizationMiddleware (line 348-357) which calls authorizeWorkloadSecret (line 241-254). That function reads the WorkloadSecretOID extension from the peer cert and requires it to match the URL path's {name} segment.
Any workload that has gone through the normal initializer / meshapi flow (coordinator/internal/meshapi/meshapi.go:71-119) and has a non-empty WorkloadSecretID in its PolicyEntry is issued a mesh cert with the matching extension, so the path-name authorisation is automatically satisfied for whichever workloadSecretID the manifest assigned to that workload. There is no rate limiting, no proof-of-work, and no audit log on triggering the panic.
what happens after the panic
net/http wraps each handler in a recovered goroutine, so the panic does not crash the Coordinator process. Instead:
- The Go runtime captures the panic, logs
http: panic serving <peer>: runtime error: slice bounds out of rangeto stderr together with a goroutine stack trace. - The connection is hung up without a response…
- Record
GHSA-3ccm-4qq2-5wrp- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-129
- Updated
- 2026-11-02
OSVGO-2026-5864 No score
Constrata's coordinator transit engine `ciphertextContainer.UnmarshalJSON` panics on attacker-controlled short ciphertexts in github.com/edgelesssys/contrast
Constrata's coordinator transit engine ciphertextContainer.UnmarshalJSON panics on attacker-controlled short ciphertexts in github.com/edgelesssys/contrast
- Record
GO-2026-5864- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
github.com/edgelesssys/contrastGo |
through 1.20.0 | 1.21.0 |
github.com/edgelesssys/contrastGo |
before 1.21.0 | 1.21.0 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-100836 is a medium vulnerability (CVSS 5.3) in Edgelesssys Contrast, published July 1, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.18%, the 7th percentile. Fixed in github.com/edgelesssys/contrast 1.21.0.
References
github.com/edgelesssys/contrast/security/advisories/GHSA-3ccm-4qq2-5wrp
vulncheck.com/advisories/edgeless-systems-contrast-through-1.20.0-denial-of-service-via-ciphertextcontainer
github.com/edgelesssys/contrast/commit/d6584fbff816037472034f7ad6e08cdbab1d870d
github.com/edgelesssys/contrast/releases/tag/v1.21.0
github.com/advisories/GHSA-3ccm-4qq2-5wrp
nvd.nist.gov/vuln/detail/CVE-2026-100836
github.com/advisories/GHSA-x66w-59x9-vvcf
github.com/CVEProject/cvelistV5/tree/main/cves/2026/100xxx/CVE-2026-100836.json
github.com/edgelesssys/contrast
Left out here: the rest of 2 source descriptions. The JSON record has them.