---
module: aria.device-posture
title: Device Posture
tab: Claude
order: 40
audience: dev
---

> Terse agent reference. Every item below confirmed in `hiveops-aria` + `hiveops-agent/hiveops-module-aria-signals`. Act without guessing.

## Ownership
- **Service:** `hiveops-aria` — port **8095**, DB **`hiveiq_aria`**, `spring.application.name=hiveops-aria`.
- **Frontend:** `hiveops-aria/frontend` → `DevicePosture.svelte`, host `aria.bcos.dev` / `aria.bcos.cloud`.
- No incident/journal/devices/mgmt involvement. No Kafka on the posture path.

## Endpoints (METHOD path → auth)
| Method | Path | Auth |
|--------|------|------|
| GET | `/api/aria/posture` | `hasRole('BCOS_ADMIN')` |
| GET | `/api/aria/posture/{deviceId}` | `hasRole('BCOS_ADMIN')` — matches **`device_id`** column, not PK |
| POST | `/api/aria/posture/report` | header `X-Service-Secret` == `aria.internal.service-secret` |

- `GET /api/aria/posture` params: `osEolStatus` (`SUPPORTED\|EOL\|UNKNOWN`), `scoreFilter` (`low`=<50, `medium`=<80), `page` (0), `size` (25; UI uses 500). `osEolStatus` wins if both set. Always sorted score ASC.
- Response: Spring `Page<DeviceSecurityPosture>` (`content[]`, `page.totalElements`).

## Storage
- **Table:** `device_security_posture` (only table for this feature). PK `id`; business key `device_agent_id`.
- Key columns: `device_id`, `institution_key`, `disk_encryption_enabled`, `audit_policy_compliant`, `software_whitelist_enabled`, `gold_image_hash`, `current_image_hash`, `os_version`, `os_eol_status`, `last_posture_check_at`, `posture_score`, `updated_at`.
- Flyway-managed, `ddl-auto=validate`.

## Kafka
- **None for posture.** (ARIA's `hiveops.aria.threats` / `hiveops.aria.sequences` are threat-event topics only — do not attribute posture to them.)

## Scoring (server-side, `DevicePostureService.calculateScore`)
- base 100, floor 0. diskEnc=false −25 · audit=false −20 · osEol=EOL −20 · whitelist=false −10 · gold≠current −20 · stale >7d −5/day cap −15. Null booleans not penalized.

## Ingest source
- Agent module `aria-signals` (`AriaPostureReporter`), `POST /api/aria/posture/report`, immediate on start + every `aria.posture.report.interval.sec` (default **86400s / 24h**).
- Windows-only booleans (BitLocker/auditpol/Solidcore). Linux → OS fields only.

## Gotchas
- Role gate is **BCOS_ADMIN only**, not `MSP_ADMIN,BCOS_ADMIN`. MSP admin → 403.
- If `aria.internal.service-secret` (`ARIA_SERVICE_SECRET`) is **blank**, `report()` returns **401 for every request** (`serviceSecret.isBlank() ||` short-circuit) → table silently stays empty. (Ingest events endpoint behaves differently — do not assume symmetry.)
- Upsert key is `deviceAgentId`; re-imaged device w/ new agent id = new row, old one orphaned.
- Blank `institutionKey` groups under `"Unknown"` in UI.
- Detail panel penalties omit the staleness penalty → displayed penalties may not sum to the score.
- `report()` merges only non-null fields; a report omitting a field keeps the prior stored value.

## Do NOT
- Do NOT expect `MSP_ADMIN` to read these endpoints.
- Do NOT add a Kafka producer/consumer for posture — it is synchronous HTTP.
- Do NOT look for posture data in incident/journal/devices/mgmt DBs — only `hiveiq_aria.device_security_posture`.
- Do NOT treat null booleans as failed checks (they are unreported, not non-compliant).
- Do NOT assume `/api/aria/posture/{deviceId}` takes the PK — it queries the `device_id` column.
- Do NOT deploy `hiveops-aria` without a non-blank `ARIA_SERVICE_SECRET` or ingest breaks.
