# D-1 Machine ID Duplication Audit

**Date:** 2026-03-29
**Branch:** culurien
**Scope:** Machine ID implementation consistency across agent codebase

---

## 1. ALL MACHINE ID IMPLEMENTATIONS

### 1a. Canonical: `system/machine_id.go`

**GetMachineID()** (line 15):
1. `machineid.ID()` — cross-platform library (Windows: Registry MachineGuid, Linux: /etc/machine-id)
2. If success: `hashMachineID(id)` → SHA256 → 64 hex chars
3. If fail: OS-specific fallback

**Linux fallback chain** (line 43):
1. `/etc/machine-id` → hash
2. `/var/lib/dbus/machine-id` → hash
3. `/sys/class/dmi/id/product_uuid` → hash
4. `/etc/hostname` + "-linux-fallback" → hash

**Windows fallback** (line 80):
1. `machineid.ID()` again (same as primary — redundant)
2. `generateGenericMachineID()` → hostname-os-arch → hash

**Generic fallback** (line 102):
`hostname-goos-goarch` → `hashMachineID()` → 64 hex chars

**Output format:** ALWAYS 64 hex characters (SHA256). Every path goes through `hashMachineID()`.

### 1b. Client: `client/client.go`

Line 36: `machineID, err := system.GetMachineID()` — calls canonical function. Cached in `Client.machineID` string field. Sent as `X-Machine-ID` header on every authenticated request (line 108).

If `GetMachineID()` fails: `machineID = ""` (empty string). Server will reject with 403 "missing machine ID header".

**Consistent with canonical:** YES.

### 1c. Registration: `cmd/agent/main.go`

Line 425: `machineID, err := system.GetMachineID()` — calls canonical function.
Line 428: **ERROR FALLBACK: `machineID = "unknown-" + sysInfo.Hostname`**

This fallback is **NOT HASHED** and **NOT 64 HEX CHARS**. It produces a string like `"unknown-my-server"` (14-30 chars, alphanumeric with dashes).

Line 443: `MachineID: machineID` — sent in `RegisterRequest.MachineID` and stored in `agents.machine_id` column.

### 1d. Example: `logging/example_integration.go`

Line 71: `machineid.ID()` — calls library DIRECTLY, bypasses `GetMachineID()` and `hashMachineID()`. Returns raw, unhashed machine ID.

**Not imported anywhere.** File is `package logging` but none of its functions are called from production code. It's dead example code.

---

## 2. ALL CALL SITES

| Location | Method | Hashed? | Consistent? |
|----------|--------|---------|-------------|
| `system/machine_id.go:17` | `machineid.ID()` → `hashMachineID()` | YES | Canonical |
| `system/machine_id.go:82` | `machineid.ID()` → `hashMachineID()` | YES | Canonical |
| `system/machine_id.go:93` | `machineid.ID()` → `hashMachineID()` | YES | Canonical |
| `system/machine_id.go:102` | hostname+os+arch → `hashMachineID()` | YES | Canonical |
| `client/client.go:36` | `system.GetMachineID()` | YES | Consistent |
| `cmd/agent/main.go:425` | `system.GetMachineID()` | YES | Consistent |
| `cmd/agent/main.go:428` | `"unknown-" + hostname` | **NO** | **DIVERGENT** |
| `logging/example_integration.go:71` | `machineid.ID()` direct | **NO** | **DEAD CODE** |

---

## 3. DIVERGENCE ANALYSIS

### 3a. Can the three production call sites return different values?

**Normal case (GetMachineID succeeds):** YES, all three return the same SHA256-hashed value. `client.go` and `main.go` both call `system.GetMachineID()`.

**Failure case (main.go line 428):**
- **Registration (main.go):** `"unknown-my-server"` — 14-30 chars, alphanumeric, NOT hashed
- **Runtime (client.go):** If `GetMachineID()` fails at client construction, `machineID = ""` — empty string → server rejects with 403

**F-D1-1 HIGH: Registration and runtime machine IDs can diverge.**
If `GetMachineID()` fails during registration, the agent registers with `"unknown-hostname"` (unhashed). On subsequent restarts, if `GetMachineID()` succeeds, the client sends a SHA256 hash. The server compares: `"unknown-hostname" != "a7f3...64hexchars..."` → **403 FORBIDDEN**. The agent is permanently locked out until re-registered.

### 3b. Server-Side Validation

`machine_binding.go:149`: `*agent.MachineID != reportedMachineID` — simple string equality comparison. No format validation. Would accept BOTH `"unknown-hostname"` (if that's what was registered) AND `"a7f3...64hex..."`. But they must match exactly.

### 3c. Registration vs Runtime Mismatch

If the agent registered with `"unknown-hostname"` (fallback) but restarts and `GetMachineID()` now succeeds (transient error resolved), the client sends a SHA256 hash that doesn't match the stored `"unknown-hostname"` → **permanent lockout**.

**F-D1-2 MEDIUM: No recovery path from machine ID mismatch.** The only fix is manual: delete the agent from the DB and re-register. There's no "update machine ID" API.

---

## 4. EXAMPLE_INTEGRATION.GO

- **Imported:** NO (zero results from grep)
- **Package:** `logging` — it IS part of the package and its functions are exported
- **Called:** NO — none of its functions are called anywhere
- **Risk:** `ExampleMachineIDMonitoring()` calls `machineid.ID()` directly (unhashed)
- **Candidate for deletion:** YES — dead example code that bypasses the canonical path

**F-D1-3 LOW:** `example_integration.go` is dead code. Its `ExampleMachineIDMonitoring()` function calls `machineid.ID()` directly, bypassing the canonical `GetMachineID()`. If anyone copies this example, they'll get unhashed machine IDs.

---

## 5. WINDOWS-SPECIFIC PATH

### 5a. Fallback Chains

**Linux:** machineid.ID() → /etc/machine-id → /var/lib/dbus/machine-id → /sys/class/dmi/id/product_uuid → hostname-linux-fallback → hostname-os-arch

**Windows:** machineid.ID() → machineid.ID() (redundant retry) → hostname-os-arch

**F-D1-4 LOW:** Windows `getWindowsMachineID()` calls `machineid.ID()` again after the primary already tried it and failed. This is a no-op retry — the same function will fail again.

### 5b. Dual-Boot Collision

Windows MachineGuid (registry) and Linux /etc/machine-id are independent. A dual-boot system produces different machine IDs for each OS. No collision risk.

### 5c. Windows Reinstall

Windows MachineGuid changes on reinstall. This is NOT documented in the codebase. After reinstalling Windows, the agent will produce a different machine ID → 403 from MachineBindingMiddleware → must re-register.

---

## 6. REGISTRATION vs RUNTIME CONSISTENCY

### 6a. Registration Path (main.go:425-443)

`system.GetMachineID()` → if error: `"unknown-" + hostname` (UNHASHED) → stored in DB

### 6b. Runtime Path (client/client.go:36-41)

`system.GetMachineID()` → if error: `""` (empty) → sent as X-Machine-ID header → server rejects with 403

### 6c. Are They Guaranteed Identical?

**NO.** Three scenarios cause divergence:

1. **Registration succeeds, runtime fails:** Registration stores SHA256 hash. Runtime sends empty string → 403.
2. **Registration fails, runtime succeeds:** Registration stores `"unknown-hostname"`. Runtime sends SHA256 hash → 403.
3. **Both fail but produce different fallbacks:** Registration uses `"unknown-hostname"` (unhashed, from main.go:428). Runtime uses empty string (from client.go:40). These don't match → 403.

**F-D1-1 is the root cause.** The main.go fallback at line 428 produces a fundamentally different format than the canonical function.

---

## 7. ETHOS CROSS-CHECK

| Principle | Status | Finding |
|-----------|--------|---------|
| ETHOS #1 | PARTIAL | GetMachineID failure logged in main.go (line 427) and client.go (line 39). But client.go uses `fmt.Printf` instead of `log.Printf`. |
| ETHOS #4 | VIOLATION | Machine ID is NOT idempotent when the error fallback activates. Registration path and runtime path produce different values for the same failure condition. |

---

## FINDINGS SUMMARY

| ID | Severity | Finding | Location |
|----|----------|---------|----------|
| F-D1-1 | HIGH | Registration fallback `"unknown-"+hostname` is unhashed and mismatches runtime path, causing permanent agent lockout on recovery | cmd/agent/main.go:428 |
| F-D1-2 | MEDIUM | No recovery path from machine ID mismatch — must delete and re-register agent | machine_binding.go:149 |
| F-D1-3 | LOW | `example_integration.go` is dead code that calls `machineid.ID()` directly (unhashed), bypassing canonical path | logging/example_integration.go:71 |
| F-D1-4 | LOW | Windows `getWindowsMachineID()` redundantly retries `machineid.ID()` after primary already failed | system/machine_id.go:82 |
| F-D1-5 | LOW | `client.go:39` uses `fmt.Printf` instead of `log.Printf` for machine ID error (ETHOS #1) | client/client.go:39 |
| F-D1-6 | INFO | Windows reinstall changes MachineGuid, causing agent lockout — not documented | system/machine_id.go |
