---
module: aria.device-posture
title: Device Posture
tab: Architect
order: 30
audience: dev
---

> **How Device Posture is built.** Grounded in `hiveops-aria` backend + `hiveops-agent/hiveops-module-aria-signals`. See also [technical.aria] for the wider ARIA service. Cross-links: [platform.data-architecture], [platform.kafka], [platform.service-ownership].

## Services involved

Device Posture is entirely **`hiveops-aria`** on the backend + the ATM agent on the collection side. It does **not** touch incident/journal/devices/mgmt for this feature. Cross-link [platform.service-ownership] — ARIA owns the `device_security_posture` table exclusively.

| Layer | Component | Role |
|-------|-----------|------|
| Agent (ATM) | `hiveops-module-aria-signals` → `AriaPostureReporter` + `AriaHttpClient` | Collects posture facts on-box and POSTs them |
| Backend API | `DevicePostureController` (`/api/aria/posture`) | Ingest (report) + admin read endpoints |
| Backend logic | `DevicePostureService` | Upsert + score calculation |
| Persistence | `DeviceSecurityPostureRepository` → `device_security_posture` | Stores one row per device (keyed by agent id) |
| Frontend | `DevicePosture.svelte` (`aria.bcos.dev`, `ariaApi.getPostures`) | Institution-grouped table + detail panel |

## Data flow (no Kafka on this path)

Unlike ARIA's threat pipeline, posture is a **synchronous HTTP upsert**, not an executor→Kafka→record-store pattern. Kafka is used elsewhere in ARIA (`hiveops.aria.threats`, `hiveops.aria.sequences`) for threat events/sequences only — **posture is not published or consumed on Kafka**. Cross-link [platform.kafka].

```
[ATM agent: aria-signals]
   AriaPostureReporter.run()  (scheduleAtFixedRate, immediate + every 86400s default)
   → collects: OS ver/EOL (all OS); BitLocker/auditpol/Solidcore (Windows only);
     SHA-256 image hash vs gold (only if aria.gold.image.hash + aria.image.path set)
   → AriaHttpClient POST /api/aria/posture/report
        header: X-Service-Secret: <aria.service.secret>
        body:   PostureReportRequest (JSON)
                │
                ▼
[hiveops-aria: DevicePostureController.report()]
   permitAll at filter level; controller verifies X-Service-Secret == aria.internal.service-secret
   (rejects 401 if header mismatches OR secret is blank)
                │
                ▼
[DevicePostureService.report()]  @Transactional
   upsert by deviceAgentId (findByDeviceAgentId → else build new)
   partial merge (only non-null fields overwrite)
   set lastPostureCheckAt = now; postureScore = calculateScore(); updatedAt = now
   → repository.save() → device_security_posture
                │
   (read side) ▼
[DevicePosture.svelte] GET /api/aria/posture?page&size&scoreFilter|osEolStatus
   BCOS_ADMIN JWT → grouped by institutionKey, avg score, <80 flags
```

## Scoring model (`DevicePostureService.calculateScore`)

Server-authoritative. Starts at **100**, floored at **0**:

| Condition | Penalty |
|-----------|---------|
| `diskEncryptionEnabled == false` | −25 |
| `auditPolicyCompliant == false` | −20 |
| `osEolStatus == EOL` | −20 |
| `softwareWhitelistEnabled == false` | −10 |
| `goldImageHash != currentImageHash` (both non-null) | −20 |
| `lastPostureCheckAt` older than 7 days | −5 per extra day, capped at −15 |

Null booleans are **not** penalized (a missing check ≠ a failed check). The frontend detail panel renders the first five penalties but not the staleness penalty.

## DB tables

| Table | R/W | Notes |
|-------|-----|-------|
| `device_security_posture` | read + write | One row per device. PK `id`; business key `device_agent_id`. Columns: `device_id`, `institution_key`, `disk_encryption_enabled`, `audit_policy_compliant`, `software_whitelist_enabled`, `gold_image_hash`, `current_image_hash`, `gold_image_validated_at`, `os_version`, `os_eol_status` (enum `SUPPORTED\|EOL\|UNKNOWN`), `last_posture_check_at`, `posture_score`, `updated_at`. |

Schema is Flyway-managed in `hiveiq_aria` with `ddl-auto=validate`. Cross-link [platform.data-architecture]. No other service mirrors this table (contrast with `device_summary` mirrors used by incident/fleet/reports/recon).

## Key API endpoints

| Method | Path | Auth | Purpose |
|--------|------|------|---------|
| POST | `/api/aria/posture/report` | `X-Service-Secret` header (permitAll at filter) | Agent/internal ingest — upsert + rescore |
| GET | `/api/aria/posture` | `hasRole('BCOS_ADMIN')` | Paged list; `osEolStatus` OR `scoreFilter` (low<50/medium<80); `page`/`size` (default 25) |
| GET | `/api/aria/posture/{deviceId}` | `hasRole('BCOS_ADMIN')` | Single record by `device_id` column |

Repository queries always sort **`OrderByPostureScoreAsc`** (worst devices first). `osEolStatus` filter takes precedence over `scoreFilter` in the controller.

## Agent collection detail

- Module: `hiveops-module-aria-signals`, active only when `aria.endpoint` is set. Config keys: `aria.endpoint`, `aria.service.secret`, `aria.posture.report.interval.sec` (default 86400).
- Windows checks shell out to `manage-bde -status C:`, `auditpol /get /subcategory:"Audit Policy Change"`, `sadmin status` (McAfee/Trellix Solidcore) with AppLocker registry fallback.
- OS EOL is classified **client-side** (Win7/XP/Vista/8 → EOL; other Windows/Linux → SUPPORTED). The backend stores whatever the agent sends.
