Return sane fallback CPU percent after first sample on unknown Unix platforms; add xvfb/scrot docs and stub tests; clear PROBLEMS.md open bugs.
13 KiB
Secure Local Payload Validation
End-to-end validation of a forged AetherForge agent without risking production keys or your daily driver. This playbook recommends one primary path and tiered alternatives.
Verdict: Docker Windows vs Disposable VM
| Environment | Server | Linux agent | Windows agent (full payload) |
|---|---|---|---|
| Host + Hyper-V VM (recommended) | ✅ devrun.bat on host |
✅ optional WSL2 VM | ✅ real Win32, WMI, USB, desktop, GPU |
| Linux Docker | ✅ Go binary in container | ✅ partial C2 + mining | ❌ cannot compile/run Windows agent features |
| Docker Desktop Windows containers | ⚠️ awkward (Server Core image) | N/A | ❌ poor fit — see below |
Why not Docker Windows for full payload tests?
Windows containers run on Windows Server Core (or similar) with a different kernel contract than desktop Windows:
- No interactive desktop — screenshots, clipboard, and many PowerShell UI calls fail or return empty.
- WMI USB subscriptions, autorun.inf, LNK spread, Defender tampering — require desktop-class Windows and often admin on a real session.
- GPU mining (T-Rex / TeamRedMiner) — no practical NVIDIA/AMD passthrough in Windows containers.
- USB propagation — container cannot see host removable drives the way a VM or bare metal can.
- WinRM / SMB lateral spread — needs a multi-machine lab network, not an isolated container namespace.
Honest recommendation: use Docker/Linux only for server + automated CI. For full Windows payload validation, use a disposable Hyper-V (or VMware) Windows 10/11 VM — or a dedicated second physical “burner” PC — then revert a snapshot when done.
Tiered Test Matrix
Tier 0 — Mining stack only (no C2, no payload)
Proves RandomX + pool worker + optional live Stratum on the dev machine.
cd agent
go run ./cmd/mine-validate -seconds 20 -threads 2
| Validates | Does not validate |
|---|---|
| RandomX engine, pool workers, live pool login | C2 WebSocket, forge bake, persistence, spread |
Note:
mine-validatemay spam[miner] hash error: randomx VM not initializedduring the multi-threaded pool phase while still reporting non-zero H/s and exiting 0. Treat non-zero hashrate + exit 0 as pass; investigate if exit code is 1.
Tier 1 — Automated CI (no real agent)
test.bat
Or faster (skip builds + Playwright):
.\scripts\test-suite.ps1 -SkipBuild -SkipE2E
| Phase | Coverage |
|---|---|
| Go server + agent unit/integration tests | API, forge, auth, pool, fusion |
| Vitest | Dashboard forms, preflight, offline gating |
Playwright (-SkipE2E off) |
Login smoke, mocked fleet UI |
Also with server already running:
.\scripts\smoke-test.ps1 -BaseUrl http://127.0.0.1:8989
Tier 2 — Server + Linux agent (same host, Linux VM, or Docker)
Good for C2 path, basic recon, mining install, systemd persistence — not Windows-only ops.
Option A — Docker (isolated bridge, recommended for CI)
# Automated proof (compose up + assert + teardown)
scripts/ci-docker-mining.sh # Linux / macOS / GHA
.\scripts\ci-docker-mining.ps1 # Windows + Docker Desktop
Or interactive:
docker compose -f docker/docker-compose.yml up --build
- Server on host port 18989; dashboard
testuser/testpass(seedocker/data/users.json). - Fleet secret
e2e-docker-fleet-secret-fixed001indocker/data/config.json+docker/agent-builtin.go. - Test wallet in
docker/data/config.jsonanddocker/agent-builtin.go(see E2E test address in security rules above). - Agent container has no internet egress — mines via server-broadcast jobs only.
- RandomX is pure Go (
go-randomx); agent image needs no CGO. - Assert logic:
GET /api/v1/health→GET /api/v1/agents(Basic auth) findsstatus=onlinewithhashrate_15s|1m|15m > 0, orGET /api/v1/dashboard/statsshowsonline_agents >= 1andtotal_hashrate > 0. Waits up to 3 minutes. - GitHub Actions:
.github/workflows/ci-docker-mining.ymlon every push (ubuntu-latest). - Verify manually: Fleet Roster shows
docker-e2e-linux; hashrate fields populate after ~30s. - Teardown:
docker compose -f docker/docker-compose.yml down --rmi local -v
Full notes: docker/README.md.
Option B — WSL2 / Linux VM
- Start server on host:
devrun.bat - Forge Linux amd64 worker pointing at
http://<host-lan-ip>:8989(VM must reach host; bind server to LAN or use Hyper-V Default Switch IP). - Run agent in WSL2 or a small Linux VM.
- Crucible:
sysinfo,pause/resume,get_log,list_dir(Linux file ops).
Tier 3 — Full Windows payload (primary E2E path)
Topology
┌─────────────────────────────┐ ┌──────────────────────────────┐
│ Host PC (control) │ LAN │ Disposable Win10/11 VM │
│ devrun.bat → :8989 │◄───────►│ forged test agent.exe │
│ data/ logs, builds, DB │ only │ snapshot "clean" → revert │
└─────────────────────────────┘ └──────────────────────────────┘
Security rules
-
Dedicated test data dir — e.g.
data-e2e\with freshusers.json; never copy productiondata\. -
Test wallet — use the repo's recommended E2E Monero address (public test wallet, not for production):
85JfUA9uyBZ2Kzv4ctoURyUYoYgpEu5QjQ8xSdiapFX8TpBHXkHwHQhBkxUxmoFKU85NH4dnSRBbiL8wSvcVmRqg4Wc9TrmSeeded automatically in
data-e2e/config.jsonbyscripts/e2e-validate.ps1and indocker/data/config.jsonfor Tier 2 Docker. Do not use your production wallet. -
Rotate fleet secret after tests if you ever pointed at shared
data\. -
No internet egress from VM (optional but ideal) — allow only host IP:8989 + pool Stratum if testing live shares.
-
Snapshot before forge run — revert VM when finished (“toss the machine”).
Step-by-Step: Tier 3 Playbook
1. Prepare isolated server data
$env:AETHERFORGE_E2E_USER = "testuser"
$env:AETHERFORGE_E2E_PASS = "testpass"
New-Item -ItemType Directory -Force -Path ".\data-e2e" | Out-Null
'{"testuser":"testpass"}' | Set-Content ".\data-e2e\users.json" -Encoding UTF8
Start server against that directory (from repo root):
bin\miner-server.exe -port 8989 -data .\data-e2e
Or use devrun.bat after pointing -data at data-e2e (or run .\scripts\e2e-validate.ps1 -PrepareOnly).
2. Calibrate for validation
In Calibrate (Settings):
| Setting | Test value |
|---|---|
| Server URL | http://<host-ip>:8989 (must match what the VM can reach) |
| Wallet | 85JfUA9uyBZ2Kzv4ctoURyUYoYgpEu5QjQ8xSdiapFX8TpBHXkHwHQhBkxUxmoFKU85NH4dnSRBbiL8wSvcVmRqg4Wc9Trm (or match data-e2e/config.json) |
file_logging |
true (required for get_log / data-e2e\logs\) |
| Stealth mode | off for first pass (easier debugging) |
| USB / share / auto-spread | off until spread is explicitly under test |
| Remote aggressive ops | on only in a VM snapshot you will revert |
agent_kill_after_days |
optional safety fuse (e.g. 1) |
3. Forge a test worker
In Forge:
- Target OS: Windows amd64
- Worker name:
e2e-validate - Enable only features you intend to test this session
- Download
e2e-validate.exe+ paireduninstall-e2e-validate.ps1
Or use the orchestrator script (API forge) after server is up:
.\scripts\e2e-validate.ps1 -ForgeAgent
4. Hyper-V VM setup (Windows)
# Example: Hyper-V Manager or PowerShell
# 1. Create Gen2 VM, 4 GB RAM, 40 GB disk
# 2. Internal or Default Switch network (host reachable)
# 3. Take snapshot named "clean-pre-agent"
# 4. Copy forged exe into VM (shared folder or ISO)
Before running the agent: snapshot name recorded, VM has no personal data, Defender policy acceptable for your lab.
5. Run agent in VM
- Execute forged exe once (install + connect).
- Confirm agent appears on dashboard Fleet Roster (online).
- Check server console for
[agent] authenticated.
6. Crucible command checklist
Run against the test agent. Tick as you go.
Core / mining
sysinfo— hostname, cores, RAMpause/resume/restart— miner controlconnectivity_probe— C2 + pool reachability JSONget_log— tail returnsminer.loglines
Recon (Windows)
ps,netstat,listen_portsipconfig,wifi,posture,patch_statusscreenshot(needs interactive logged-in desktop)camera_list/camera_snapshot(needs ffmpeg + camera)
Files (Windows)
list_dir—C:\Users\<user>\read_file— small text fileupload/downloadround-trip
Power / lifecycle
exec/powershell— benign echo commandreboot_machine— only if snapshot revert planneduninstallor rununinstall-*.ps1— M-10 checklist
Advanced (enable in forge + revert snapshot after)
get_wifi_passwords,defender_off,firewall_*- USB spread — second USB passthrough device
spread_now/ LAN spread — requires second lab VM
7. Collect logs
| Source | Path / action |
|---|---|
| Server stdout | devrun.bat window |
| Server agent log cache | data-e2e\logs\<agent-id>.log |
| Agent on disk | %LOCALAPPDATA%\<install-dir>\miner.log |
| Dashboard | Fleet → Remote Control → Fetch Log |
| API | GET /api/v1/agents/{id}/log?refresh=1 |
Get-ChildItem ".\data-e2e\logs\"
Get-Content ".\data-e2e\logs\<agent-id>.log" -Tail 100
8. Teardown (“toss the machine”)
- VM: Hyper-V → Revert to snapshot
clean-pre-agent(or delete VM). - Host: stop server; archive or delete
data-e2e\if no longer needed. - Network: remove any temporary firewall rules allowing VM→host:8989.
- Secrets: if production
data\was ever used, rotate fleet secret in Calibrate.
Quick orchestration
# Automated tiers 0–1 + instructions for tier 3
.\scripts\e2e-validate.ps1
# Tier 0 only
.\scripts\e2e-validate.ps1 -SkipAutomatedTests -SkipServerCheck
# Prepare test data + print checklist (no forge)
.\scripts\e2e-validate.ps1 -PrepareOnly
What each existing tool covers
| Tool | Tier | Scope |
|---|---|---|
agent/cmd/mine-validate |
0 | Mining only |
test.bat / scripts/test-suite.ps1 |
1 | Full automated suite |
scripts/smoke-test.ps1 |
1 | REST B-01–B-10 |
scripts/ci-docker-mining.sh / .ps1 |
2 | Docker Linux agent hashrate proof |
.github/workflows/ci-docker-mining.yml |
2 | GHA push gate |
server/web/e2e/*.spec.ts |
1 | Dashboard smoke (mocked WS) |
docs/TEST_RESULTS.md |
1–3 | Matrix IDs; M-01–M-09 manual |
devrun.bat |
2–3 | Build + launch control server |
| Forge + burner VM | 3 | Full payload |
Linux vs Windows agent capabilities (validation scope)
| Feature | Windows | Linux | macOS |
|---|---|---|---|
| RandomX CPU mining + dashboard hashrate | ✅ | ✅ | ✅ |
| GPU RVN mining | ✅ | stub (detect only) | stub |
| Screenshot | ✅ (GDI+) | ✅ (scrot/import/gnome-screenshot) | ✅ (screencapture) |
| Camera | ✅ (ffmpeg) | ✅ (V4L2/ffmpeg/fswebcam) | stub |
| Clipboard | ✅ | ✅ (xclip/pbpaste) | ✅ (pbpaste) |
| File browser Crucible | ✅ | ✅ | ✅ |
| Posture / syscheck | ✅ (WMI) | ✅ (ufw/systemd/apt) | partial |
| Firewall aggressive ops | ✅ (netsh) | ✅ (ufw/iptables) | stub |
| USB / WMI spread | ✅ | ❌ | ❌ |
| SMB / WinRM spread | ✅ | ❌ | ❌ |
| SSH lateral spread | ❌ | ✅ | ✅ |
| WiFi password harvest | ✅ | stub | stub |
| Defender off | ✅ | N/A (returns error) | N/A |
| systemd / LaunchAgent persistence | — | ✅ | ✅ |
| Idle-mode mining (CPU % sample) | ✅ | ✅ (fixed: /proc/stat) | ✅ (sysctl kern.cp_time) |
| Headless Linux screenshot | N/A | ✅ with Xvfb — see below | N/A |
Use Tier 3 Windows VM when validating spread, GPU, screenshot, or aggressive ops. Use Tier 2 Linux for faster C2 regression on recon + mining.
Linux headless screenshot
Docker/CI/VPS agents have no real display. scrot, import, and gnome-screenshot need an X server.
Install and start a virtual framebuffer:
apt-get install -y xvfb scrot
Xvfb :99 -screen 0 1280x720x24 &
export DISPLAY=:99
Then run the Crucible Screenshot action as usual (agent picks up scrot on PATH).
One-shot custom command (Crucible screenshot command field — no persistent DISPLAY in the agent process):
DISPLAY=:99 Xvfb :99 -screen 0 1280x720x24 & sleep 1; scrot -q 55 /tmp/s.jpg && base64 -w0 /tmp/s.jpg
On Alpine or minimal images, use the agent forge custom screenshot command field with the same pattern.
Unsupported platforms (freebsd, etc.) return screenshot not supported on this platform from screenshot_stub.go.
References
- Automated test phases:
tests/README.md - Manual matrix IDs:
docs/TEST_RESULTS.md(M-01–M-10) - Known gaps:
PROBLEMS.md