high Threat analysis

shopsprint/decimal Go Module DNS Backdoor Typosquat

The Go module github.com/shopsprint/decimal typosquatted github.com/shopspring/decimal and used an init-time DNS TXT command loop in v1.3.3.

#supply-chain#go#typosquatting#dns#backdoor
On this page 0% read

    Executive Summary

    Socket reported that github.com/shopsprint/decimal is a long-running one-character typosquat of the legitimate github.com/shopspring/decimal Go module Socket. GBHackers noted that the module sat clean for years to establish reputation before receiving its weaponized v1.3.3 update GBHackers. The malicious v1.3.3 release added an init() goroutine that queried DNS TXT records and executed returned commands, giving attackers a stealthy command channel inside any application importing the typoed module, bypassing basic firewall layers Cybersecurity News.

    The source repository and owner were removed by disclosure time, but Go’s public module proxy continued to serve the cached module artifact because proxy.golang.org permanently caches published module zips Go Modules. Defenders must search source, go.mod, go.sum, vendored code, build caches, and released binaries for the typoed module path, replace it with the canonical github.com/shopspring/decimal path, and enforce checksum/proxy hardening guidelines CyberPress.

    Key Facts

    threat_type: "Go module typosquat with DNS TXT backdoor"
    ecosystem: "Go modules"
    registry: "proxy.golang.org and pkg.go.dev"
    affected_packages:
      - "github.com/shopsprint/decimal"
    malicious_versions:
      - "v1.3.3"
    known_good_versions:
      - "github.com/shopspring/decimal legitimate module path"
    fixed_or_safe_versions:
      - "replace typoed path with github.com/shopspring/decimal and rebuild from clean caches"
    execution_trigger: "Go package init() when imported by a program"
    primary_impact: "remote command execution through DNS TXT command channel"
    campaign_context: "Long-lived typosquat disclosed during the May 2026 supply-chain incident wave."
    confidence: "high"
    canonical_source: "https://socket.dev/blog/popular-go-decimal-library-typosquat-dns-backdoor"
    last_verified: "2026-05-24"

    Source Confidence & Evidence Mapping

    • confirmed: Socket identifies github.com/shopsprint/[email protected] as the malicious Go module version and contrasts it with the legitimate github.com/shopspring/decimal package Socket.
    • confirmed: The malicious diff added network, command-execution, and timing imports plus an init() goroutine in decimal.go Socket.
    • confirmed: The command channel uses DNS TXT queries to dnslog-cdn-images[.]freemyip[.]com and executes returned content Socket.
    • confirmed: Socket states the Go Module Proxy still served the cached malicious v1.3.3 artifact after the GitHub source disappeared Socket.
    • unclear: Public reporting does not prove the number of downstream builds or binaries that imported the typoed path.

    Impact Determination

    ClassificationCriteriaRequired evidenceRequired actionClosure condition
    Confirmed compromisegithub.com/shopsprint/[email protected] is present and Go package import executes init() DNS backdoor or the reported process, file, or network indicators is observed.Artifact inventory plus runtime telemetry showing Go package import executes init() DNS backdoor or listed C2/process/file indicators.Isolate affected hosts or runners, preserve artifacts, and rotate reachable credentials from a clean environment.Affected artifacts are removed, exposed credentials are replaced, and downstream audit modules show no suspicious follow-on use.
    Presumed exposedgithub.com/shopsprint/[email protected] was installed, pulled, imported, built, or executed during the exposure window, but telemetry cannot prove exfiltration.Lockfile, package cache, workflow, image pull, extension inventory, build log, or deployment record tied to the exposure window.Rebuild from clean artifacts and rotate credentials available to the affected environment.Credential owners confirm revocation of old material and clean artifacts are deployed.
    Potentially exposedThe package, workflow, image, extension, or module appears in dependency or deployment records, but Go module initialization is not established.Manifest, lockfile, build, deployment, or endpoint records plus a named telemetry gap.Collect the missing execution and telemetry evidence before narrowing scope.Every hit is dispositioned as confirmed compromise, presumed exposed, or not exposed.
    Not exposedNo affected version, artifact, mutable reference, or indicator appears in source, lockfiles, build outputs, deployments, package caches, or runtime telemetry.Repository search, dependency inventory, build/deployment export, package cache query, and runtime telemetry query results.Preserve the negative search output and keep the prevention controls active.Search evidence covers developer endpoints, CI runners, production deployments, and package or image caches.
    UnknownRequired inventory, build, endpoint, network, or audit telemetry is unavailable.A gap statement naming unavailable systems, owners, and time windows.Keep the asset in scope and make conservative rotation or rebuild decisions for high-value environments.The missing evidence is recovered or the risk owner accepts residual uncertainty.

    Minimum Evidence To Collect

    minimum_evidence:
      - "Dependency, workflow, extension, image, or module inventory covering developer endpoints, CI runners, and production deployments."
      - "Positive or negative search results for v1.3.3."
      - "Execution evidence for Go package import executes `init()` DNS backdoor."
      - "Process, file, DNS, proxy, firewall, or package-manager telemetry for listed indicators."
      - "Inventory of credentials, tokens, deployment paths, and downstream systems reachable from exposed environments."

    Timeline

    • 2017-11-08 The typoed module is registered on GitHub as a clean placeholder package to establish trust GBHackers.
    • 2023-08-19T09:27:21Z The attacker uploads the weaponized v1.3.3 version containing the init() backdoor Socket.
    • 2026-05-19 Socket publishes public analysis of the typosquat and DNS backdoor Socket.
    • 2026-05-20 Go module registry team flags module paths and updates pkg.go.dev indexing rules Go Modules.
    • 2026-05-24 This local feed split creates a standalone Go typosquat article instead of grouping it into a weekly roundup.

    What Happened

    The attacker relied on one-character package-name confusion: shopsprint instead of shopspring. That typo is easy to miss in go.mod, import statements, and vendored paths because the package API mimicked the legitimate decimal library closely enough for normal builds to succeed. Go developers are encouraged to lock down module configurations and checksum validations to prevent accidental inclusion of typosquats CyberPress.

    The malicious v1.3.3 release added a Go init() function. In Go, init() runs automatically when the package is imported, before application code explicitly calls package functions. That means affected applications could start a command loop simply by importing the dependency.

    Technical Analysis

    Initial Access

    No upstream compromise of the legitimate shopspring/decimal project is reported. The attack is dependency confusion through typosquatting: publish a similarly named module and wait for developers or automated tools to import the wrong path.

    Package or Artifact Tampering

    Socket’s diff analysis shows a small malicious change in decimal.go: added imports for networking, command execution, and timing, plus an init() goroutine. This is effective because the rest of the package remains close to the legitimate API surface Socket.

    Execution Trigger

    The trigger is Go package initialization. Any application, test, or tool that imports github.com/shopsprint/decimal can execute the malicious init() path.

    Payload Behavior

    The payload performs periodic DNS TXT lookups and executes command content returned through DNS. This design can bypass simple HTTP egress controls if DNS logging and TXT-query policy are weak, offering attackers an extremely stealthy execution mechanism inside private network parameters Cybersecurity News.

    Exfiltration / C2

    The primary C2 channel is DNS TXT for dnslog-cdn-images[.]freemyip[.]com. The use of a dynamic DNS domain and TXT records makes DNS query-type telemetry important for hunting Socket.

    Propagation

    No autonomous propagation is reported. Persistence comes from build reproducibility infrastructure: proxy.golang.org and other module proxy caches can continue serving the cached malicious v1.3.3 module zip even after the upstream GitHub source repository is deleted Go Modules.

    Obfuscation or Evasion

    The main evasion is name similarity plus minimal diff size. Because the malicious behavior lives in init(), reviewers can miss it if they focus only on public API changes.

    Affected Assets and Blast Radius

    affected_assets:
      ecosystems:
        - "Go modules"
      packages:
        - "github.com/shopsprint/decimal"
      versions:
        - "v1.3.3"
      repositories:
        - "github.com/shopsprint/decimal"
      ci_cd_systems:
        - "Go build/test pipelines"
      container_images:
        - "images built from applications importing the typoed path"
      developer_tools:
        - "go command"
        - "Go module proxy/cache"
    credentials_at_risk:
      - "secrets available to affected build hosts or applications, depending on executed commands"
    not_currently_known_to_affect:
      - "github.com/shopspring/decimal when spelled correctly."

    Indicators of Compromise

    package_versions:
      - "github.com/shopsprint/decimal v1.3.3"
    files:
      - "go.mod"
      - "go.sum"
      - "decimal.go"
    hashes:
      - "f31bdd069fe7966ae11be1f78ee5dd44445938856dd1df12379e0e84a6851f5c"
    domains:
      - "dnslog-cdn-images[.]freemyip[.]com"
      - "freemyip[.]com"
    urls: []
    ips: []
    process_patterns:
      - "Go application importing github.com/shopsprint/decimal"
    network_patterns:
      - "TXT query to dnslog-cdn-images[.]freemyip[.]com every five minutes"
    provenance_signals:
      - "module path differs from github.com/shopspring/decimal by one character"
      - "Go Module Proxy serves cached module after source repository removal"

    Detection and Hunting

    Script: local repository and exported telemetry scope

    #!/usr/bin/env python3
    import os
    import sys
    import json
    import subprocess
    from pathlib import Path
    
    ROOT = sys.argv[1] if len(sys.argv) > 1 else "."
    LOG_ROOT = os.environ.get("LOG_ROOT", "")
    OUT = Path(os.environ.get("OUT", "hp-shopsprint-decimal-go-typosquat-scope"))
    SINCE = "2023-08-19T09:27:21Z"
    UNTIL = "2026-05-24T23:59:59Z"
    
    PACKAGES = [
      "github.com/shopsprint/decimal",
    ]
    VERSIONS = [
      "v1.3.3",
      "github.com/shopsprint/decimal v1.3.3",
    ]
    FILES = [
      "go.mod",
      "go.sum",
      "decimal.go",
    ]
    DOMAINS = [
      "dnslog-cdn-images.freemyip.com",
      "freemyip.com",
    ]
    URLS = [
    ]
    IPS = [
    ]
    HASHES = [
      "f31bdd069fe7966ae11be1f78ee5dd44445938856dd1df12379e0e84a6851f5c",
    ]
    PROCESS_PATTERNS = [
      "Go application importing github.com/shopsprint/decimal",
    ]
    NETWORK_PATTERNS = [
      "TXT query to dnslog-cdn-images.freemyip.com every five minutes",
    ]
    
    # Positive signal: repository, lockfile, artifact, process, or network telemetry contains one of the exact incident selectors above.
    # Escalation: any match tied to a production build, CI run, deployed asset, or secret-bearing host moves the asset to presumed exposed.
    
    OUT.mkdir(parents=True, exist_ok=True)
    indicators_file = OUT / "indicators.txt"
    
    # Collect unique indicators
    indicators = set()
    for group in [PACKAGES, VERSIONS, FILES, DOMAINS, URLS, IPS, HASHES, PROCESS_PATTERNS, NETWORK_PATTERNS]:
        for val in group:
            if val:
                indicators.add(val)
    
    with open(indicators_file, "w") as f:
        for ind in sorted(indicators):
            f.write(ind + "\n")
    
    print(f"[+] Written unique selectors to {indicators_file}")
    
    # Walk local directory
    print(f"[+] Scanning directory: {ROOT} for selectors...")
    matches = []
    exclude_dirs = {"node_modules", "vendor", "dist", ".git"}
    for root, dirs, filenames in os.walk(ROOT):
        dirs[:] = [d for d in dirs if d not in exclude_dirs]
        for filename in filenames:
            filepath = Path(root) / filename
            try:
                content = filepath.read_text(errors="ignore")
                for ind in indicators:
                    if ind in content:
                        matches.append(f"{filepath}: found '{ind}'")
            except Exception:
                pass
    
    if matches:
        (OUT / "repository-indicator-matches.txt").write_text("\n".join(matches) + "\n")
        print(f"[!] Found {len(matches)} matches in codebase!")
    
    # Optional Log Scanning
    if LOG_ROOT and os.path.exists(LOG_ROOT):
        print(f"[+] Scanning telemetry log directory: {LOG_ROOT}...")
        log_matches = []
        for root, _, filenames in os.walk(LOG_ROOT):
            for filename in filenames:
                filepath = Path(root) / filename
                try:
                    content = filepath.read_text(errors="ignore")
                    for ind in indicators:
                        if ind in content:
                            log_matches.append(f"{filepath}: found '{ind}'")
                except Exception:
                    pass
        if log_matches:
            (OUT / "exported-telemetry-indicator-matches.txt").write_text("\n".join(log_matches) + "\n")
            print(f"[!] Found {len(log_matches)} matches in logs!")
    
        if PACKAGES:
            registry_dir = OUT / "registry"
            registry_dir.mkdir(exist_ok=True)
            for package in PACKAGES:
                if not package: continue
                safe_name = package.replace("/", "__")
                print(f"[+] Querying go list for {package}...")
                env = os.environ.copy()
                env["GONOSUMDB"] = "*"
                res = subprocess.run(["go", "list", "-m", "-json", package], capture_output=True, text=True, env=env)
                if res.returncode == 0:
                    (registry_dir / f"go-{safe_name}.json").write_text(res.stdout)
    
    print(f"[+] Wrote scope artifacts under {OUT}")

    Downstream Abuse Audits

    Script: GitHub organization run, release, secret, and workflow audit

    #!/usr/bin/env python3
    import os
    import sys
    import json
    import subprocess
    from pathlib import Path
    
    if "ORG" not in os.environ:
        print("ERROR: Set ORG environment variable to the GitHub organization to audit", file=sys.stderr)
        sys.exit(1)
    
    ORG = os.environ["ORG"]
    SINCE = "2023-08-19T09:27:21Z"
    UNTIL = "2026-05-24T23:59:59Z"
    OUT = Path(os.environ.get("OUT", "hp-shopsprint-decimal-go-typosquat-github-audit"))
    
    SELECTORS = [
      "github.com/shopsprint/decimal",
      "v1.3.3",
      "github.com/shopsprint/decimal v1.3.3",
      "go.mod",
      "go.sum",
      "decimal.go",
      "dnslog-cdn-images.freemyip.com",
      "freemyip.com",
      "f31bdd069fe7966ae11be1f78ee5dd44445938856dd1df12379e0e84a6851f5c",
    ]
    
    # Positive signal: a workflow run, release, secret, key, package, or workflow change overlaps the exposure window and references an incident selector.
    # Remediation trigger: unauthorized post-exposure write activity or a secret-bearing run matching an incident selector requires token revocation and downstream cloud/registry review.
    
    OUT.mkdir(parents=True, exist_ok=True)
    (OUT / "runs").mkdir(exist_ok=True)
    (OUT / "logs").mkdir(exist_ok=True)
    (OUT / "repos").mkdir(exist_ok=True)
    
    # 1. Write incident-selectors file
    selectors_file = OUT / "incident-selectors.txt"
    with open(selectors_file, "w") as sf:
        for s in SELECTORS:
            if s:
                sf.write(s + "\n")
    
    # 2. Get list of repos
    print(f"[+] Fetching repositories for organization: {ORG}")
    repo_res = subprocess.run(["gh", "repo", "list", ORG, "--limit", "1000", "--json", "nameWithOwner"], capture_output=True, text=True)
    if repo_res.returncode != 0:
        print(f"[-] Failed to fetch repos: {repo_res.stderr}", file=sys.stderr)
        sys.exit(1)
    
    repos = [r["nameWithOwner"] for r in json.loads(repo_res.stdout)]
    
    for repo in repos:
        safe_repo = repo.replace("/", "__")
        print(f"[+] Auditing repository: {repo}")
    
        # Check runs in the window
        runs_res = subprocess.run([
            "gh", "api", f"/repos/{repo}/actions/runs",
            "-f", "per_page=100",
            "-f", f"created=>={SINCE}",
            "--paginate"
        ], capture_output=True, text=True)
    
        if runs_res.returncode == 0:
            try:
                all_runs = json.loads(runs_res.stdout).get("workflow_runs", [])
                filtered_runs = [r for r in all_runs if r["created_at"] <= UNTIL]
    
                if filtered_runs:
                    with open(OUT / "runs" / f"{safe_repo}-runs.jsonl", "w") as rf:
                        for run in filtered_runs:
                            rf.write(json.dumps(run) + "\n")
    
                            # Fetch log dynamically
                            run_id = str(run["id"])
                            log_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--log"], capture_output=True, text=True)
                            if log_res.returncode == 0:
                                (OUT / "logs" / f"{safe_repo}-{run_id}.log").write_text(log_res.stdout)
    
                            # Fetch details
                            view_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--json", "databaseId,workflowName,headSha,event,createdAt,jobs"], capture_output=True, text=True)
                            if view_res.returncode == 0:
                                (OUT / "runs" / f"{safe_repo}-{run_id}.json").write_text(view_res.stdout)
            except Exception as e:
                print(f"[-] Error parsing runs for {repo}: {e}")
    
        # Check releases in window
        subprocess.run(["gh", "api", f"/repos/{repo}/releases", "-f", "per_page=100", "--paginate"], capture_output=True)
        # Check repo secrets updated in window
        subprocess.run(["gh", "api", f"/repos/{repo}/actions/secrets", "-f", "per_page=100", "--paginate"], capture_output=True)
        # Check deploy keys
        subprocess.run(["gh", "api", f"/repos/{repo}/keys", "-f", "per_page=100", "--paginate"], capture_output=True)
    
    # Scan output directory for any indicator selector matches
    print("[+] Scanning gathered telemetry for indicator matches...")
    subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(selectors_file), str(OUT)], capture_output=False)
    
    print(f"[+] Wrote GitHub audit artifacts under {OUT}")

    Script: cloud OIDC and deployment credential follow-on audit

    #!/usr/bin/env python3
    import os
    import json
    import subprocess
    from pathlib import Path
    
    SINCE = "2023-08-19T09:27:21Z"
    UNTIL = "2026-05-24T23:59:59Z"
    OUT = Path(os.environ.get("OUT", "hp-shopsprint-decimal-go-typosquat-cloud-audit"))
    AWS_REGIONS = os.environ.get("AWS_REGIONS", "us-east-1").split(",")
    
    # Positive signal: token exchange or privileged write activity occurs in the exposure window from GitHub, CI/CD, package registry, or deployment automation identity.
    # Remediation trigger: unexpected write, deploy, IAM, secret, or registry activity tied to an exposed CI/CD path requires trust-policy disablement and credential rotation.
    
    OUT.mkdir(parents=True, exist_ok=True)
    
    # 1. AWS CloudTrail Audit
    print("[+] Querying AWS CloudTrail for Web Identity token exchanges...")
    aws_events = []
    for region in AWS_REGIONS:
        res = subprocess.run([
            "aws", "cloudtrail", "lookup-events",
            "--region", region,
            "--start-time", SINCE,
            "--end-time", UNTIL,
            "--lookup-attributes", "AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity",
            "--output", "json"
        ], capture_output=True, text=True)
    
        if res.returncode == 0:
            try:
                events = json.loads(res.stdout).get("Events", [])
                for event in events:
                    ct = json.loads(event.get("CloudTrailEvent", "{}"))
                    ct["region"] = region
                    aws_events.append(ct)
            except Exception as e:
                print(f"[-] Error parsing AWS CloudTrail events: {e}")
    
    if aws_events:
        with open(OUT / "aws-assume-role-with-web-identity.jsonl", "w") as f:
            for ev in aws_events:
                f.write(json.dumps(ev) + "\n")
    
        # Audit follow-on events for returned access keys
        for ev in aws_events:
            access_key = ev.get("responseElements", {}).get("credentials", {}).get("accessKeyId")
            region = ev.get("region", "us-east-1")
            if access_key:
                print(f"[+] Enumerating AWS events for AccessKey: {access_key}")
                f_res = subprocess.run([
                    "aws", "cloudtrail", "lookup-events",
                    "--region", region,
                    "--start-time", SINCE,
                    "--end-time", UNTIL,
                    "--lookup-attributes", f"AttributeKey=AccessKeyId,AttributeValue={access_key}",
                    "--output", "json"
                ], capture_output=True, text=True)
                if f_res.returncode == 0:
                    try:
                        f_events = json.loads(f_res.stdout).get("Events", [])
                        with open(OUT / "aws-follow-on-api-calls.jsonl", "a") as ff:
                            for fe in f_events:
                                ff.write(fe.get("CloudTrailEvent", "{}") + "\n")
                    except Exception as e:
                        print(f"[-] Error writing follow-on events: {e}")
    
    # 2. Azure Activity Log Audit
    print("[+] Querying Azure activity logs...")
    az_res = subprocess.run([
        "az", "monitor", "activity-log", "list",
        "--start-time", SINCE,
        "--end-time", UNTIL,
        "--query", "[?contains(operationName.value, 'write') || contains(operationName.value, 'delete') || contains(operationName.value, 'Microsoft.ManagedIdentity')]",
        "-o", "json"
    ], capture_output=True, text=True)
    
    if az_res.returncode == 0:
        (OUT / "azure-write-delete-activity.json").write_text(az_res.stdout)
    
    # 3. GCP Logging Audit
    print("[+] Querying GCP Cloud Logging...")
    gcp_filter = f'timestamp>="{SINCE}" AND timestamp<="{UNTIL}" AND (protoPayload.methodName="google.sts.v1.SecurityTokenService.ExchangeToken" OR protoPayload.methodName:"GenerateAccessToken" OR protoPayload.methodName:"CreateServiceAccountKey" OR protoPayload.methodName:"SetIamPolicy")'
    gcp_res = subprocess.run([
        "gcloud", "logging", "read", gcp_filter,
        "--format", "json"
    ], capture_output=True, text=True)
    
    if gcp_res.returncode == 0:
        (OUT / "gcp-token-and-iam-activity.json").write_text(gcp_res.stdout)
    
    print(f"[+] Wrote cloud audit artifacts under {OUT}")

    Script: registry metadata and artifact audit

    #!/usr/bin/env python3
    import os
    import json
    import subprocess
    from pathlib import Path
    
    SINCE = "2023-08-19T09:27:21Z"
    OUT = Path(os.environ.get("OUT", "hp-shopsprint-decimal-go-typosquat-registry-audit"))
    PACKAGES = [
      "github.com/shopsprint/decimal",
    ]
    VERSIONS = [
      "v1.3.3",
      "github.com/shopsprint/decimal v1.3.3",
    ]
    
    # Positive signal: registry metadata, package tarballs, or cached artifacts contain the exact affected package/version values.
    # Remediation trigger: any internal package cache, build artifact, or deployment using these package/version values requires exposure scoping.
    
    OUT.mkdir(parents=True, exist_ok=True)
    
    with open(OUT / "affected-versions.txt", "w") as av:
        for version in VERSIONS:
            if version:
                av.write(version + "\n")
    
        # 1. Audit Go dependencies in project files
        print("[+] Scanning Go module files...")
        for file in ["go.mod", "go.sum"]:
            if Path(file).exists():
                subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(OUT / "affected-versions.txt"), file])
    
        # 2. Query Go module metadata
        metadata_dir = OUT / "metadata"
        metadata_dir.mkdir(exist_ok=True)
        for package in PACKAGES:
            if not package: continue
            safe_name = package.replace("/", "__")
            print(f"[+] Querying go list for {package}...")
            env = os.environ.copy()
            env["GONOSUMDB"] = "*"
            res = subprocess.run(["go", "list", "-m", "-json", package], capture_output=True, text=True, env=env)
            if res.returncode == 0:
                (metadata_dir / f"go-{safe_name}.json").write_text(res.stdout)
    
        # 3. HOW TO REVOKE AND ROTATE EXPOSED GO PRIVATE MODULE CREDENTIALS:
        # Private Go modules typically use Git SSH keys or HTTPS personal access tokens:
        # 1. Revoke the exposed GitHub/GitLab token:
        #    subprocess.run(["gh", "api", "-X", "DELETE", "/user/tokens/123456"])
        # 2. Revoke the SSH deploy key if compromised:
        #    subprocess.run(["gh", "repo", "deploy-key", "delete", "123456", "--repo", "my-org/my-repo"])
        # 3. Generate a new token and update git credentials or CI/CD secrets:
        #    subprocess.run(["gh", "secret", "set", "GO_PRIVATE_TOKEN", "--body", "my-new-token"])
    
    print(f"[+] Wrote registry audit artifacts under {OUT}")

    Sources

    1. Socket: Popular Go Decimal Library Targeted by Long-Running Typosquat with DNS Backdoor - Role: PRIMARY_RESEARCH - Impact: Documents the typoed module path, malicious version, artifact diff, DNS TXT C2, proxy persistence, hash, and remediation.
    2. Go Modules: proxy.golang.org Caching Behavior - Role: REGISTRY_PERSISTENCE - Impact: Confirms why deleted GitHub source repositories remain downloadable through Go’s decentralized dependency caches.
    3. Cybersecurity News: DNS TXT Records Abused as Command-and-Control in Go Modules - Role: DNS_C2_ANALYSIS - Impact: Analyzes the timing anomalies, query frequency, and stealth profile of DNS-based command loops.
    4. GBHackers: Chronological Analysis of shopsprint Module Poisoning - Role: CHRONOLOGICAL_REPORTING - Impact: Explains how the library was maintained in a clean state for years before weaponization in version 1.3.3.
    5. CyberPress: Hardening Go Projects Against Typosquatting Campaigns - Role: COMPLIANCE_GUIDELINE - Impact: Provides steps to lock down private Go mod proxies and enforce checksum verification rules.

    IOC Clipboard

    6 IOCs
    Defang IOCs
    domain dnslog-cdn-images.freemyip.com dnslog-cdn-images[.]freemyip[.]com
    domain freemyip.com freemyip[.]com
    hash f31bdd069fe7966ae11be1f78ee5dd44445938856dd1df12379e0e84a6851f5c f31bdd069fe7966ae11be1f78ee5dd44445938856dd1df12379e0e84a6851f5c
    file go.mod go.mod
    file go.sum go.sum
    file decimal.go decimal.go