# MacLockdown — Registro de Arquitectura y Cambios

**Versión estable:** 0.8
**Fecha:** 2026-03-16
**Estado:** ✅ Export completo · PCI DSS integrado · fixes recalibrados · admin on-demand · UI/score alineados con `unknown`

---

## Resumen ejecutivo

MacLockdown sigue siendo un motor de auditoría de seguridad para macOS con UI nativa y catálogo data-driven en `checks.json`, pero esta iteración consolidó cuatro áreas críticas del producto:

1. El flujo de export ahora genera PDF completos sin truncamiento y reportes más honestos cuando existen controles no verificables.
2. El producto ya soporta una capa normativa adicional para `PCI DSS`, tanto en UI como en export Markdown/PDF.
3. El pipeline de fixes y scans fue recalibrado para distinguir correctamente entre preferencias de usuario, cambios privilegiados y variaciones reales de macOS Tahoe.
4. La experiencia dejó de pedir privilegios al iniciar; la elevación ahora ocurre bajo demanda durante fixes privilegiados y no bloquea el scan inicial.

La arquitectura sigue siendo data-driven. El schema continúa en `checks.json` v1.2, pero ahora incorpora referencias `PCI DSS` y calibraciones específicas para Tahoe. La app incorpora además dos piezas nuevas de infraestructura: `AdminSession.swift` y `OSDetector.swift`.

---

## Métricas del producto (v0.8)

| Métrica | Valor |
|---|---|
| Controles auditados | 39 |
| Dominios | System (11) · Disk (4) · Firewall (3) · Network (14) · Privacy (7) |
| Perfiles | Basic (9) · Hardened (36) · Paranoid (39) |
| Tipos de acción | Auto (27) · Manual (8) · Info (4) |
| Frameworks de referencia | CIS · NIST · Apple · PCI DSS |
| Exportes | Markdown + PDF completo |
| Vectores de ataque | 6 |
| Archivos Swift | 16 |
| Plataforma validada | macOS Tahoe 26.3 · Apple Silicon M5 |
| Flujo de privilegios | On-demand — no se solicita admin al abrir la app |

---

## Mapa de archivos

| Archivo | Rol actual | Cuándo tocarlo |
|---|---|---|
| `checks.json` | Catálogo v1.2 — controles, perfiles, fixes, mappings PCI DSS | Agregar controles, mappings o recalibrar comandos |
| `ContentView.swift` | UI completa — header, lista, footer, compare, badges, acciones | Cambiar presentación, navegación o affordances |
| `ReportEngine.swift` | Export Markdown/PDF + comparación PCI DSS + secciones `unknown` | Cambiar formato del reporte |
| `SecurityViewModel.swift` | Orquestador del flujo de scan, fix, export e ignore | Cambiar flujo de negocio |
| `ScanEngine.swift` | Interpretación de checks y calibración por ID | Ajustar lectura de estado real |
| `FixEngine.swift` | Aplicación de fixes por contexto de ejecución | Corregir fixes automáticos |
| `ShellEngine.swift` | Ejecución shell/AppleScript | Cambiar primitives de ejecución |
| `AdminSession.swift` | Cache de privilegios admin y elevación on-demand | Cambiar estrategia de privilegios |
| `OSDetector.swift` | Detección legible de versión/plataforma | Cambiar branding de OS en UI/reportes |
| `SecurityCheck.swift` | Modelo de datos + enum `PCI DSS` | Si cambia el schema |
| `PersistenceEngine.swift` | UserDefaults | Cambiar persistencia |
| `ScoreEngine.swift` | Score ponderado excluyendo `ignored`, `checking`, `unknown` | Cambiar algoritmo |
| `MacLockdownApp.swift` | Entry point y ventana | Cambiar app shell |

---

## 1. Schema v1.2 — `checks.json`

### Nuevo framework de referencia: `PCI DSS`

El schema no cambió de versión, pero sí su semántica funcional. Ahora los checks pueden mapearse también a `PCI DSS`, usando el mismo patrón de `frameworks[]`:

```json
{
  "framework": "PCI DSS",
  "control": "PCI DSS 8.2.2"
}
```

### Mappings agregados

Se añadieron referencias PCI DSS a controles ya existentes, entre ellos:

`auto_updates` · `filevault` · `firewall` · `guest_user` · `audit_logging` y otros controles con evidencia local suficiente para device hardening.

### Cambios de catálogo relevantes

- `remote_apple_events` ajustó su `checkCommand` para usar primero `systemsetup` y alinear mejor scan + fix.
- `diagnostics_sharing` alineó su `fixCommand` con lo que el scan realmente valida.
- `guest_user` normalizó el nombre visible a `Guest User Disabled`.

### Controles sensibles a Tahoe

En hardware real de Tahoe hubo claves/comandos que no devolvían valor estable. Para esos checks se adoptó un comportamiento conservador:

`screensaver_timeout` · `bluetooth_sharing` · `diagnostics_sharing` · `safari_suggestions` · `tcp_timestamps`

Cuando no hay evidencia suficiente, el resultado pasa a `unknown` en lugar de `fail`.

---

## 2. Modelo — `SecurityCheck.swift`

### Frameworks de referencia

El enum `SecurityFramework` ahora incluye:

```swift
case cis    = "CIS"
case nist   = "NIST"
case apple  = "Apple"
case pciDSS = "PCI DSS"
```

No cambió la forma del modelo principal, pero sí su capacidad semántica: un check puede contribuir ahora a una comparación `PCI DSS` sin requerir otro engine o schema paralelo.

---

## 3. Arquitectura de engines

```text
SecurityViewModel  (@MainActor · orquestador)
├── CheckLoader          lee checks.json → [SecurityCheck]
├── ScanEngine           evalúa cada check por ID
│     └── ShellEngine    ejecuta bash / AppleScript
├── ScoreEngine          score ponderado
├── FixEngine            aplica fixes por modo de ejecución
├── ReportEngine         genera Markdown y PDF completos
├── PersistenceEngine    UserDefaults
├── AttackSurfaceEngine  analiza vectores de ataque
├── AdminSession         cachea elevación admin on-demand
└── OSDetector           expone versión/plataforma legible
```

### Cambio estructural importante

Antes, el scan pedía privilegios al inicio. Ahora la arquitectura separa explícitamente:

- scan sin elevación inicial
- fixes privilegiados bajo demanda
- checks con lectura privilegiada marcados como `unknown` si no existe sesión admin

Eso reduce fricción y evita prompts innecesarios al abrir o escanear la app.

---

## 4. ReportEngine — PDF y Markdown

### Export PDF completo

El flujo anterior renderizaba HTML en un `WKWebView` con tamaño A4 fijo y dependía de un delay temporal, lo que podía truncar el contenido.

Ahora el flujo es:

```text
exportPDF()
  1. buildHTML()
  2. WKWebView offscreen
  3. esperar fin real de navegación
  4. medir altura completa del documento
  5. createPDF() con el alto renderizado
  6. escribir archivo y revelar en Finder
```

Resultado: el PDF ya no sale cortado cuando el reporte excede una sola página lógica.

### Sección `PCI DSS Compare`

Tanto PDF como Markdown incluyen ahora una sección normativa adicional con:

- mapped score
- covered requirements
- gap requirements
- manual evidence required
- out of product scope

La comparación evita afirmar “compliance completa” desde evidencia local del endpoint.

### Soporte para `unknown`

El export ahora incluye:

- `Unverified` en el resumen
- banner de verificación parcial
- sección dedicada `UNVERIFIED`

Con esto, el total del reporte cierra de forma honesta contra:

`passed + failed + unverified + ignored`

---

## 5. UI — Compare, score y affordances

### Compare Baseline

La vista `Compare` dejó de ser solo CIS y ahora soporta selector:

`CIS` / `PCI DSS`

La vista de `PCI DSS` separa:

- covered by device checks
- mapped gaps
- manual evidence required
- not covered by this product

### Score y mensaje principal

Se corrigió una incoherencia importante de UX:

si existían controles `unknown`, la UI podía mostrar `39 controls` junto a `33 passed / 0 failed` y además seguir presentando el score como si todo estuviera verificado.

Ahora:

- la barra de stats muestra `unverified`
- el footer ya no dice `0 issues found` si todavía hay controles sin evidencia
- el label principal se degrada a `X controls unverified`

### Restauración de botones por fila

Tras introducir `unknown`, la UI había perdido:

- `IGNORE`
- fix por control
- `APPLY ALL FIXES`

El comportamiento fue restaurado tratando `unknown` como estado accionable para UI, igual que `fail`, cuando existe `fixCommand`.

---

## 6. FixEngine — contexto correcto de ejecución

### Problema corregido

Los fixes automáticos se ejecutaban todos como admin. Eso rompía varios controles que escriben preferencias del usuario actual, porque `defaults write` se aplicaba al contexto de `root`.

### Solución

`FixEngine` ahora distingue dos modos:

```text
ExecutionMode.user
ExecutionMode.privileged
```

### Controles movidos a contexto de usuario

`airdrop` · `ad_tracking` · `siri_analytics` · `screensaver_timeout` · `bluetooth_sharing` · `diagnostics_sharing` · `safari_suggestions` · `media_sharing`

### Mejoras adicionales

- los comandos privilegiados se normalizan quitando `sudo` redundante antes de pasar por AppleScript
- un fix exitoso marca la `AdminSession` como concedida, para reutilizar esa elevación después
- tras aplicar un fix ya no se marca `PASS` de forma optimista; se revalida el control real

---

## 7. ScanEngine — calibración de checks y Tahoe

### Servicios del sistema endurecidos

Se reforzó la interpretación de:

`firewall` · `file_sharing` · `remote_apple_events` · `screen_sharing` · `internet_sharing` · `audit_logging`

Se añadieron fallbacks por proceso cuando `launchctl` no era suficientemente estable.

### `audit_logging`

El check dejó de depender solo de:

```text
launchctl list com.apple.auditd
```

Ahora usa fallback a:

- `pgrep -x auditd`
- `ps -p ... -o comm=`

Eso eliminó el falso negativo que seguía apareciendo en reporte aunque `auditd` estuviera vivo.

### `guest_user`

El check quedó identificado como candidato frágil por salida real de `dscl`. El nombre visible fue normalizado y la calibración queda explícitamente documentada como área a seguir validando si el estado mostrado no coincide con la máquina real.

### Checks sin evidencia estable

En Tahoe, varios comandos devolvieron salida vacía o inconsistente. En lugar de penalizar al usuario con falsos `FAIL`, la estrategia actual es:

- `unknown` si no hay evidencia suficiente
- exclusión de `unknown` del score
- visualización explícita en UI y reportes

---

## 8. Flujo de privilegios — `AdminSession.swift`

### Cambio de experiencia

Antes:

- abrir la app o correr scan podía disparar un prompt de admin inmediatamente

Ahora:

- la app no pide admin al iniciar
- el scan arranca sin elevar
- la elevación ocurre solo al ejecutar un fix privilegiado

### Semántica actual

`AdminSession` mantiene una sesión cacheada:

```swift
func hasAccess() -> Bool
func markGranted()
func requestIfNeeded() async -> Bool
```

`requestIfNeeded()` sigue existiendo para flujos que sí quieran elevar explícitamente, pero el arranque normal ya no la usa.

---

## 9. Calidad y seguridad del código

Durante la sesión se corrigieron problemas estructurales detectados en review:

- carrera entre prompt admin y scan
- posible deadlock en `ShellEngine.run()` por drenar `Pipe` después de `waitUntilExit()`
- perfil persistido ignorado fuera de beta
- fixes marcando `PASS` sin revalidación real
- warnings de aislamiento/concurrencia Swift 6

### `ShellEngine.run()`

La lectura del pipe ocurre ahora antes de `waitUntilExit()`, evitando el deadlock clásico cuando un proceso llena el buffer.

### Persistencia de perfil

El perfil guardado vuelve a respetarse fuera de beta. La app ya no fuerza el mismo perfil de manera errónea cuando no corresponde.

---

## 10. Calibración en hardware real

**Plataforma:** MacBook Pro M5 14", macOS Tahoe 26.3

### Comandos validados manualmente en máquina real

Se usó evidencia de Terminal para recalibrar:

- `audit_logging`
- `bonjour_advertising`
- `screensaver_timeout`
- `bluetooth_sharing`
- `diagnostics_sharing`
- `safari_suggestions`
- `tcp_timestamps`

### Resultado de calibración

- `auditd` confirmado vivo vía `launchctl`/`pgrep`
- `bonjour_advertising` devolviendo `1` en Tahoe
- múltiples claves `defaults` ausentes o sin salida en Tahoe, tratadas como `unknown`

### Efecto práctico

Se eliminaron falsos `FAIL` y el producto pasó a distinguir mejor entre:

- incumplimiento real
- evidencia parcial
- controles no verificables en esa versión de macOS

---

## 11. Reportes y resultados de la sesión

Durante la sesión se generaron múltiples reportes de validación. El resultado estable del ciclo de corrección fue:

- último falso negativo eliminado en `audit_logging`
- cobertura `PCI DSS 10.2.1` restablecida
- score consistente con el estado real del sistema

La capa final de UX corrigió además la presentación de `unknown`, para que el reporte y la UI no vendan un `100/100` como “todo verificado” cuando en realidad todavía existen controles no verificables.

---

## 12. Beta / distribución

`BetaConfig.isActive` está actualmente en `false`, por lo que el proyecto queda documentado desde el comportamiento de la versión completa.

Esto implica:

- selector de perfil activo
- Compare disponible
- Attack Surface disponible
- export PDF/Markdown completo
- título de ventana sin sufijo beta

---

## 13. Cambios puntuales por archivo en esta sesión

| Archivo | Cambio principal |
|---|---|
| `AdminSession.swift` | Nuevo archivo para cachear privilegios admin |
| `OSDetector.swift` | Nuevo archivo para exponer versión/plataforma legible |
| `ReportEngine.swift` | PDF completo · secciones `unknown` · comparación PCI DSS |
| `SecurityCheck.swift` | Framework `PCI DSS` |
| `checks.json` | mappings PCI DSS · fixes calibrados · nombre `Guest User Disabled` |
| `ContentView.swift` | selector compare CIS/PCI · stats `unverified` · restauración de botones |
| `SecurityViewModel.swift` | revalidación post-fix · perfil persistido · admin on-demand |
| `FixEngine.swift` | separación user/privileged |
| `ScanEngine.swift` | calibración Tahoe · servicios del sistema · `audit_logging` |
| `ShellEngine.swift` | prevención de deadlock |
| `BetaConfig.swift` | configuración sin aislamiento problemático |
| `MacLockdownApp.swift` | título condicionado por beta sin warnings |

---

## 14. Pendiente

### Backlog técnico

- Recalibrar definitivamente `guest_user` contra más salidas reales de `dscl`
- Buscar fuentes alternativas para convertir algunos `unknown` de Tahoe de vuelta a `pass/fail` reales
- endurecer `CheckLoader` para degradar con error visible en vez de `fatalError`

### Backlog de producto

- Exportar una portada visual más fuerte para la sección `PCI DSS`
- Añadir diferenciación visual entre score alto y score alto pero parcialmente verificado
- Evaluar un baseline normativo adicional si aparecen más frameworks de comparación
- Explorar expansión de `Compliance` hacia normativas financieras LATAM como iniciativa futura, comenzando por Chile (CMF / RAN 20-10) y evaluando marcos equivalentes en Brasil, Colombia, México y Argentina

### Backlog de gobernanza y versionamiento

- Formalizar la documentación de cambios como tarea obligatoria de cierre por sprint, beta, RC o release
- Mantener un documento `ARCHITECTURE_*.md` por hito con fecha, estado, cambios funcionales, calibraciones por macOS/hardware y decisiones reversibles
- Registrar explícitamente cambios en score, `checks.json`, mappings normativos, comportamiento de `unknown` y flujo de privilegios
- Definir una convención fija de naming para hitos futuros, por ejemplo `ARCHITECTURE_v10_RC1.md` o `CHANGELOG_v0.8.0.md`
- Mantener derivados ejecutables de negocio y GTM en `BUSINESS_POSITIONING_*.md`, `PACKAGING_PRICING_*.md` y `MESSAGING_DERIVATIVES_*.md`
