Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain
-
AccessObtain target record ULID via API response
-
DeliverySubstitute first character with non-canonical alias (8, G, or R)
-
ExploitSubmit aliased ULID to security-checked endpoint
-
ExecutionServer resolves correct database row
-
PersistString-level check treats alias as a distinct unseen key
-
ImpactIdempotency or deny-list guard is bypassed
Vulnerability Assessment
| Weakness |
CWE
|
| Exploitation Conditions | Exploitation requires three concurrent conditions: (1) the target application runs ash_double_entry 0.1.0 through 1.0.18 with AshDoubleEntry.ULID-typed fields exposed as caller-controlled string identifiers over an HTTP or API boundary; (2) application logic performs security-sensitive string equality comparisons over those raw identifier strings, such as idempotency deduplication, deny-list lookups, audit log correlation, or signature verification computed over the submitted string value; and (3) the application does not independently normalize or bounds-check ULID strings before these comparisons. Deployments that generate ULIDs server-side and use them only as opaque database primary keys - never evaluating attacker-supplied ULID strings in security-sensitive equality checks - are not practically exploitable. The CVSS 4.0 AT:P metric correctly captures that these conditions are not universally present. |
| Impact | Real-world risk is low. The vendor CVSS 4.0 score of 2.1 (VI:L, SI:L, all other impacts N) accurately reflects that this is a narrowly scoped integrity issue rather than a broad compromise primitive. The AT:P (attack target prerequisite present) metric correctly signals that exploitation depends on conditions not present in every deployment: the application must expose ULID-typed fields as caller-controlled string identifiers and must perform security-sensitive equality comparisons over those raw strings without prior normalization. Deployments using ULIDs purely as opaque database keys - where the server generates and stores them but never evaluates attacker-supplied strings for security decisions - are not practically at risk. No EPSS score was provided in the input data, so exploitation probability cannot be quantified numerically; the absence of a CISA KEV entry and any public exploit code aligns with the low severity assessment. The vendor's CVSS 4.0 AV:L metric appears conservative given the advisory explicitly describes an HTTP and API attack surface; this discrepancy is noted and the actual network-reachable attack path is captured in the assessed vector. Overall this is a correctness and integrity hygiene issue: even a small operator running an Ash-based accounting service in a city like boleslawiec that relies on idempotency keys for payment deduplication could see real business-logic impact, but broad internet-scale exploitation is implausible without specific conditions being met. |
| Exploit ScenarioDerived | An attacker obtains a target record's ULID from a prior API response and constructs three alternate spellings by substituting the first character with 8, G, or R - each decoding to the same 16-byte value as the original. Submitting the alternate-spelled ULID in a subsequent operation guarded by an idempotency key causes the server to locate and mutate the correct database record while the string-equality check treats the alternate spelling as a previously unseen key, permitting the operation to execute multiple times. No public proof-of-concept code has been identified. |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv1 – Sep 07, 2026
ULID first-character non-canonicality in ash_double_entry (Elixir) exposes a multi-spelling identifier aliasing flaw affecting versions 0.1.0 through 1.0.18. The AshDoubleEntry.ULID type encodes 128-bit identifiers as 26 Crockford base-32 characters, but the first character carries only 3 usable bits - canonical values are 0 through 7 - while the library's decode and validation functions accepted all 32 possible characters there, allowing 8, G, and R to silently decode to the identical 16-byte value as their canonical counterpart. An attacker who can submit caller-controlled ULIDs over an HTTP or API boundary can exploit this to circumvent application-layer string comparisons such as idempotency keys, deduplication guards, deny-list lookups, or audit-trail correlation, causing the server to resolve the intended record while treating the alternate spelling as a distinct key. No public exploit has been identified at time of analysis.
RemediationAI
Upgrade ash_double_entry to version 1.0.19 or later, which enforces that the first Crockford character of any accepted ULID decodes to a value no greater than 7, ensuring each ULID has exactly one canonical spelling. The upstream fix is at commit d3e688d300a581ae214b3ca7d95ef4de63fbb050 (https://github.com/ash-project/ash_double_entry/commit/d3e688d300a581ae214b3ca7d95ef4de63fbb050) and the advisory is at https://github.com/ash-project/ash_double_entry/security/advisories/GHSA-qxp2-vgp9-268q. As a compensating control prior to patching, applications can add an explicit first-character validation step at every API input boundary that accepts ULID strings - rejecting any ULID whose first character is outside the set {0,1,2,3,4,5,6,7} in the Crockford alphabet before passing it to library functions. This must be applied consistently at every boundary where ULIDs serve as idempotency keys, deny-list entries, or audit identifiers; missing a single boundary preserves the bypass path. There are no known side effects from the patched library behavior beyond stricter rejection of malformed inputs.
Affected VersionsENISA
Affected Packages
Source: OSV (Open Source Vulnerabilities)
Affects your stack
Affects 1 package in euvd
Checking this CVE against your own stacks and servers is part of the Basic plan and up.
Technical ContextAI · show details
ash_double_entry is an Elixir library (CPE: cpe:2.3:a:ash-project:ash_double_entry) built on the Ash Framework, providing double-entry accounting primitives. Its ULID encoding places 125 bits of information into 26 Crockford base-32 characters (26 × 5 = 130 bits total), leaving 5 unused high bits in the first character; only values 0-7 (3 bits) are therefore reachable by the encoder. The CWE-173 (Improper Handling of Alternate Encoding) root cause existed in two locations in lib/ulid.ex: decode/1 masked the first character to its low 3 bits without rejecting out-of-range inputs, and valid?/1 accepted all 32 Crockford alphabet members in the first position. The fixing commit d3e688d adds an explicit guard - if d(c1) > 7, do: throw(:error) - to decode/1, and tightens valid?/1 with an additional d(c1) <= 7 predicate, ensuring every accepted ULID has exactly one canonical string representation and that encode(decode(x)) == x holds for all accepted inputs. The accompanying test suite in test/ulid_test.exs confirms that alternate first characters 8, G, and R are now rejected by both cast_input/2 and dump_to_native/2.
Know when Ash Double Entry gets its next vulnerability
- Watch products and get an email when something new is published
- Check this CVE against your own stack and servers
- Exploit and PoC references, threat intelligence from 22 sources
No credit card · 7-day full trial
Severity by source
CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:L/SA:N
Network-exploitable via API with no authentication required, but AC:H because security-sensitive string-equality checks over caller-supplied ULID identifiers are a non-default, application-specific pattern.
Primary rating from Vendor (EEF).
CVSS VectorVendor: EEF
DescriptionCVE.org
Improper Handling of Alternate Encoding vulnerability in ash-project ash_double_entry allows an attacker to submit several distinct string spellings of the same identifier.
AshDoubleEntry.ULID renders a 128-bit ULID as 26 Crockford base-32 characters, but the first character encodes only 3 bits, so canonical values are 0 to 7. decode/1 in lib/ulid.ex masks the first character to its low 3 bits and valid?/1 accepts all 32 characters in that position, so 0..., 8..., G... and R... decode to the identical 16-byte value and resolve to the same row. When the type is exposed as a public ID over an HTTP or API boundary, an attacker-supplied ID can be spelled differently from the record it actually reads or writes, desynchronizing or bypassing string-level checks such as idempotency and deduplication keys, deny-lists, audit correlation, or signatures computed over the submitted ID.
This issue affects ash_double_entry: from 0.1.0 before 1.0.19.
Threat Intelligence Overview
How this decision was reached
The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.
EUVD-2026-72541
NVD
CVE.org
EPSS
Vulnrichment
GitHub
Exploit-DB