# Remove Hardware ID Binding **Task**: AZ-197_remove_hardware_id **Name**: Remove hardware ID binding from resource flow (admin-side cleanup) **Description**: Remove `CheckHardwareHash`, `UpdateHardware`, `HardwareService`, the `PUT /users/hardware/set` endpoint, and the hardware-hash component of API encryption-key derivation. The threat this protected against (credential reuse across machines via desktop installers) no longer exists in the target architecture. **Complexity**: 3 points **Dependencies**: None **Component**: Admin API **Tracker**: AZ-197 **Epic**: AZ-181 ## Problem The `Hardware` field on `User` and the `CheckHardwareHash` flow were designed to bind a user account to a specific physical machine, preventing credential reuse across machines when users had desktop installers. The target architecture has eliminated that threat: - **Edge devices** ship as **secured Jetsons with fTPM** (secure boot, fTPM-protected key storage, no user filesystem access, no desktop installers distributed). Hardware identity is anchored in the fTPM, not in a SHA-384 of CPU/GPU/Memory/DriveSerial strings. - **Server / desktop access** uses the **SaaS** path (browser → admin API). There is no installer to copy and no hardware fingerprint to take. - The Loader component itself has been **architecturally retired** (Scenario X = Watchtower + rclone + flight-gate; see `suite/_docs/_repo-config.yaml` `unresolved:loader-retirement-arch-doc` and the assumptions log entries dated 2026-04-19). Provisioning was relocated from `loader/scripts/` to `suite/_infra/provisioning/`. There is no `loader/` workspace in the suite anymore, so this ticket is now **purely admin-side**. The hardware binding therefore adds: - Unnecessary complexity in the encryption-key derivation chain. - A real production failure mode (`HardwareIdMismatch`, error code 40) on legitimate drive-replacement / fTPM-attested rotations. - A maintenance cost on every endpoint and DTO that still carries the `Hardware` field. ## Outcome - Resource download flow no longer requires a hardware fingerprint. - API encryption-key derivation simplified to email + password only. - All admin-API hardware-binding code paths removed. - Hardware-binding tests removed (unit + e2e); other tests updated to stop sending `Hardware`. - DB column `User.Hardware` left in place but nullable and unused — no migration in this ticket (separate cleanup ticket if/when desired). ## Scope ### Included — Admin API production code - Remove `CheckHardwareHash` and `UpdateHardware` from `IUserService` / `UserService` (`Azaion.Services/UserService.cs`). - Remove `PUT /users/hardware/set` endpoint from `Azaion.AdminApi/Program.cs`. - Simplify `POST /resources/get/{dataFolder}` (`Program.cs` + `Azaion.Services/ResourcesService.cs`): remove `request.Hardware` parameter usage; derive encryption key without the hardware hash. - Simplify `POST /resources/check`: remove the hardware-binding side-effect entirely. If the endpoint becomes purely a "do I have any newer resources?" probe, keep it; if it becomes a no-op shell, remove it (decide based on what consumers still call today). - Update `Security.GetApiEncryptionKey` (`Azaion.Services/Security.cs`) to drop the `hardwareHash` parameter from its signature; derive the key from `email + password` only. - Remove (do not deprecate) `Security.GetHWHash` — the codebase has no Loader to coordinate with anymore. - Remove `SetHWRequest` DTO (`Azaion.Common/Requests/SetHWRequest.cs`). - Remove the `Hardware` property usage from `GetResourceRequest` (`Azaion.Common/Requests/GetResourceRequest.cs`). The wire field may still be **accepted** (deserialized and ignored) for one release cycle to keep any in-flight legacy clients from breaking on 400s — pick the simplest of (drop entirely / accept-and-ignore) and document the choice in the implementation report. - Remove `HardwareIdMismatch` and `BadHardware` from `Azaion.Common/BusinessException.cs` `ExceptionEnum`. - Leave `User.Hardware` column in DB (nullable, unused). No migration here. ### Included — Tests in this workspace - Delete `e2e/Azaion.E2E/Tests/HardwareBindingTests.cs` entirely (every test in that file asserts behaviour that is being removed). - Update `e2e/Azaion.E2E/Tests/ResourceTests.cs`, `ResilienceTests.cs`, `SecurityTests.cs` to stop sending the `Hardware` field on resource calls (or to assert the field is ignored, whichever matches the chosen wire-compat policy above). - Update `Azaion.Test/UserServiceTest.cs` and `Azaion.Test/SecurityTest.cs` to remove tests asserting hardware-hash behaviour and to drop the `hardwareHash` argument from any retained `GetApiEncryptionKey` calls. - Trim test fixtures in `db-init/` and `Azaion.Test` if they seed a `User.Hardware` value purely to satisfy hardware-binding flows. ### Included — Workspace docs (pointer-only updates, no full rewrite) - Mark `_docs/02_document/diagrams/flows/flow_hardware_check.md` as obsolete (header note + link to AZ-197 implementation report) — full deletion is fine if cleaner. - Mark `_docs/02_document/modules/common_requests_set_hw.md` as obsolete (the documented module no longer exists). - Note in `_docs/02_document/architecture.md` (Security & Encryption section) that API encryption-key derivation no longer includes a hardware hash, and that `User.Hardware` is a tombstoned column. ### Excluded - Database migration to drop the `hardware` column from `users` (separate ticket if/when desired; harmless to leave nullable). - Changes to user registration or login flow (those don't touch the hardware path). - Any change to the suite-level `_docs/00_top_level_architecture.md` "Security & Encryption" / "Binary Split Security" sections — that's part of `unresolved:loader-retirement-arch-doc` and is owned at the suite level, not by this ticket. - Live-device decommissioning of fielded Loader containers (separate ops runway, tracked as a sibling of `loader-retirement-arch-doc`). - Anything in the `loader/` workspace — it does not exist in the suite anymore. ## Acceptance Criteria **AC-1: Resource download works without hardware** Given a provisioned device user with valid email and password When `POST /resources/get/{dataFolder}` is called without a `Hardware` field Then the resource is returned and decrypts successfully using a key derived from email + password only **AC-2: Hardware-set endpoint is gone** Given the updated admin API When `PUT /users/hardware/set` is called with any payload Then the response is 404 **AC-3: Encryption-key derivation is simplified** Given the updated `Security.GetApiEncryptionKey` When it is called with `(email, password)` Then it returns the key derived from `email + password` only — there is no `hardwareHash` parameter on the public signature **AC-4: Hardware-binding tests are gone** Given the updated test projects When the test suite is built and listed Then `HardwareBindingTests` does not exist and no remaining test asserts `HardwareIdMismatch` / error code 40 / hardware-hash binding **AC-5: Resource calls in remaining tests do not send `Hardware`** Given the updated `ResourceTests`, `ResilienceTests`, `SecurityTests` When the resource-download / resource-check requests are inspected Then no test sends a `Hardware` field on any resource request (or, if accept-and-ignore wire-compat was chosen, tests assert the response is unchanged whether `Hardware` is present or absent) **AC-6: ExceptionEnum no longer has hardware codes** Given `Azaion.Common/BusinessException.cs` When the `ExceptionEnum` is read Then `HardwareIdMismatch` and `BadHardware` entries are gone, and no production code references them **AC-7: Build is clean** Given the workspace after the changes When `dotnet build` runs across the solution Then it completes with no errors and no new warnings introduced by this ticket **AC-8: Test suite passes** Given the workspace after the changes When the existing test suite (`Azaion.Test` + `e2e/Azaion.E2E`) is run via `docker-compose.test.yml` Then all tests pass (the deleted `HardwareBindingTests` are not counted) ## Constraints - Wire-compat policy on the `Hardware` field of resource requests must be chosen explicitly (drop / accept-and-ignore) and recorded in the implementation report — this is the only consumer-facing contract change in the ticket. - Do not rename `User.Hardware` column or drop it from the entity in this ticket; only stop reading/writing it. Renaming/dropping requires a separate migration ticket per the workspace's "no rename without confirmation" rule. ## Cross-architecture context This ticket is the admin-side half of an architectural transition that has already happened: - Loader retirement (Scenario X) — `suite/_docs/_repo-config.yaml` → `unresolved:loader-retirement-arch-doc` - Suite-root restructure (2026-04-19) — see assumptions_log entries in the same file - Admin-side hardware-binding cleanup — **this ticket** (AZ-197) The matching suite-doc refresh (top-level architecture, Binary Split Security section) is tracked separately under the unresolved item above and is intentionally NOT in this ticket's scope.