# Security policy — Holger console (holger-ui)
## Report a vulnerability privately
Write to **holger@holger.rs**, and start the subject line with `SECURITY:`. Do not open a
public issue for a vulnerability. The console follows Holger's security policy: what to
put in a report, the acknowledgement within 14 days, the fix or mitigation target, the
supported versions and how fixes are announced are all in `SECURITY.md` in the
repository `nordisk/holger`. Anyone can clone it with
`git clone ssh://anonymous@git.gunnar.rs:2222/nordisk/holger.git`.
The rest of this file is the console's security model (in Swedish).
---
## Säkerhetsmodell — holger console
> Underhållet beslutdokument. Ändra inte tokenarkitekturen, CORS-posturen
> eller transportvägen utan att uppdatera det här dokumentet först.
## Arkitekturinvarianter (bevaras av varje ändring)
- **Backend är auktoritativ.** Varje operation kontrolleras av serverns
auth + RBAC + audit vid anropet. Konsolen har ingen klient-RBAC; capability-
information (`WhoAmI`, `writable`-flaggor) används enbart för upptäckbarhet.
- **Avslag visas ordagrant.** RBAC-, quarantine-, promote- och policyfel
renderas med serverns exakta mening — aldrig omskrivna, aldrig maskerade som
framgång. Partiella resultat (`failed > 0`, `!complete`) presenteras aldrig
som full framgång.
- **gRPC-web direkt, same-origin.** Webbläsaren talar gRPC-web med servern via
`/grpc` på samma origin (dev: Trunk-proxy; prod: nginx). Ingen REST, ingen
BFF, ingen CORS-öppning på servern.
- **Inga cookies, inga client secrets, ingen WebAuthn.** OIDC är authorization
code + PKCE (publik klient, S256, engångs-verifier, state-eko). WebAuthn är
medvetet inte runtime-anslutet i backend och får inte "färdigställas" härifrån.
- **Fail-closed-egenskaperna** (preview/dry-run, seal-once, append-only audit,
read-gate) ägs av servern och konsolen speglar dem: replikering är
preview-först med låst Execute, promotion bär seal-once-konsekvensen i en
explicit bekräftelse.
## Tokenhantering och hotmodell
**Beslut:** bearer-tokenen ligger i `sessionStorage` och bifogas av EN
interceptor (`grpc.rs`) som `authorization: Bearer` på varje anrop.
- **Varför inte cookies:** en cookie skulle införa CSRF-yta och kräva
serverförändringar; sessionStorage + explicit header har ingen ambient
credential — en främmande sida kan inte få webbläsaren att skicka tokenen.
- **Risken:** allt sidans JS kan läsa kan en XSS läsa. Mitigering i lager:
1. **Minimal beroendeyta** — inga externa script, ingen CDN, inga
tredjeparts-JS-paket; hela appen är egen Rust→wasm.
2. **Strikt CSP** (genereras per release av `cargo xtask publish`):
`default-src 'none'`, script enbart `'self'` + hash på trunks enda
inline-bootstrap + `'wasm-unsafe-eval'`, och **`connect-src 'self'`** —
även om skript injiceras kan det inte exfiltrera till främmande origin.
OIDC-deployer lägger explicit till IdP-origin (`IDP_ORIGIN=`).
3. **Ingen token i URL, logg eller diagnostik** — verifierat: koden har inga
`console.log`; felmodellen (`grpc::Fail`) bär endast statuskod, serverns
meddelande och tidsstämpel; tokenfältet är `type="password"`; gRPC-statusar
ekar aldrig credentials (serverns felmeddelanden är statiska meningar).
4. **Livslängd:** sessionStorage dör med fliken; Log out raderar explicit;
OIDC-verifier/state är engångs och förbrukas även vid misslyckande.
- **Vid misstänkt läcka:** rotera hemligheten (t.ex.
`~/.config/holger-console/token`, 0600, läses via auth-stegens `token_file`)
och starta om servern — konfigen bär ingen inline-hemlighet.
## Transport och drift
- Produktionsexponering sker **alltid bakom TLS** — referensdeployen
terminerar i `tailscale serve` (ts.net-cert); nginx och holger-server
lyssnar enbart på loopback. En annan LB/proxy ska ge samma garantier
(TLS + SPA-fallback till `index.html` + `/grpc`-montering).
- Airgap: konsolen renderar helt utan internet — inga fonter, script eller
bilder utifrån (CSP:n förbjuder det dessutom aktivt).
## Kända avvägningar
- `style-src 'unsafe-inline'`: temat ligger som ett `<style>`-element i
`index.html`. Ofarligt i sig (style-injektion kräver redan DOM-access) men
kan skärpas till hash om stilen flyttas till en fil.
- `WhoAmI` svarar även anonyma anrop (identitet är inte hemlig för den som
frågar om sig själv) och ekar aldrig credentialen — endast subject/metod/roll.
- Quarantine-orakel: konsolen visar serverns avslag ordagrant men listar inte
quarantine-styrda artefakter annorlunda än andra — ingen existens-läcka
utöver vad servern själv säger.