# Home Assistant Stelloauth Add-on Design ## Purpose Build a maintainable Home Assistant OS custom add-on repository that runs Stelloauth locally for the Stellantis Vehicles integration. The add-on replaces the shared OAuth worker without changing Home Assistant OS, bypassing Supervisor, or installing Docker Compose. The finished repository must install from Home Assistant's add-on store, start and stop through Supervisor, survive restarts, keep CloakBrowser's CDP endpoint private, and provide one exact Login service URL for the integration. ## Verified target environment The target was inspected read-only in Home Assistant on 2026-09-24: - Home Assistant OS 18.2 - Home Assistant Core 2026.9.3 - Home Assistant Supervisor 2026.09.2 - Container architecture `amd64` - CPU architecture `x86_64` - Machine type `ova` on KVM - Home Assistant host address `192.168.1.20` - Approximately 4 GB free disk space at inspection time The add-on will also declare and test `aarch64`, because both required upstream images publish Linux arm64 manifests. ## Sources and pinned versions The initial add-on version is `0.1.0` and pins these upstream inputs: | Component | Version | Source commit or OCI index digest | | --- | --- | --- | | Stelloauth | `v0.6.0` | `367d4f8c02a3b072c59142c49dffc129edc8548b` | | Stelloauth image contract | `v0.6.0` | `sha256:51b2194ec9b80cc484d11c016ec5436a12161277a493afdab7078959966d5aa9` | | CloakBrowser wrapper/image | `0.5.10` | `f04c23da285b3b3d3cf10c8f9d282e7adc1d52ce` / `sha256:2ed5b2d047cbdde22cde7ef1a796526c716aadaa5bccbe1db5ade49282b64a76` | | Go builder | `1.27.1-bookworm` | `sha256:69a7b9788769bec032d238959b61854e9ae87f57be9029ec04e9885fabf99195` | | Stellantis Vehicles integration | `2026.9.4` | `9e0ef96f8fe478af291da4c38c923ada78d0ebf6` | The Stelloauth and CloakBrowser OCI indexes contain both `linux/amd64` and `linux/arm64` manifests. The Dockerfile will use index digests so Docker selects the manifest matching the Supervisor build architecture. ## Architecture decision Use one add-on container with two supervised application processes: ```text Home Assistant Core | | POST http://0031621f-stelloauth:8080/worker v Stelloauth 0.0.0.0:8080 | | CDP http://127.0.0.1:9222 v CloakBrowser 127.0.0.1:9222 ``` A small Python process manager is the container command. Supervisor's Docker init remains enabled and acts as PID 1/subreaper. The manager starts CloakBrowser, proves CDP readiness, starts Stelloauth, monitors both processes, forwards termination, and performs bounded graceful shutdown. If either process exits unexpectedly, the manager stops the other process and exits nonzero so Supervisor can restart the add-on. This design keeps both upstream applications as separate processes while giving them one lifecycle and a loopback-only CDP connection. ### Rejected alternatives Two add-ons were rejected because CloakBrowser would have to listen on the shared Supervisor network, installation order and cross-add-on discovery would be more fragile, and shutdown/readiness would span two Supervisor lifecycles. A derived prebuilt public image was rejected because the CloakBrowser Binary License prohibits redistributing or repackaging the binary. The repository therefore contains build instructions only. The user's Supervisor obtains the official CloakBrowser image and creates an image solely for internal use. A host port enabled by default was rejected because `/worker` accepts MyOpel credentials and has no authentication. The supported internal Supervisor network does not expose the listener on the LAN. ## Repository structure ```text repository.yaml README.md LICENSE stelloauth/ config.yaml Dockerfile README.md DOCS.md CHANGELOG.md translations/ da.yaml en.yaml rootfs/ usr/local/bin/addon-supervisor patches/ stelloauth-security.patch cloakserve-loopback.patch tests/ test_addon_metadata.py test_supervisor.py test_security_patches.py test_runtime.sh ``` No `build.yaml` is used. Current Home Assistant documentation states that the legacy builder no longer reads it. The Dockerfile provides an explicit base image and the required `io.hass.*` labels. ## Build design The Dockerfile has two stages: 1. A pinned Go builder clones the Stelloauth `v0.6.0` tag, verifies that HEAD is exactly `367d4f8c02a3b072c59142c49dffc129edc8548b`, applies the maintained security patch, runs the relevant Go tests, and builds a static binary. 2. The runtime stage derives from the official pinned CloakBrowser `0.5.10` image, applies the MIT-licensed wrapper patch, copies the patched Stelloauth binary and process manager, clears the upstream entrypoint, and starts the manager as the container command. The proprietary CloakBrowser Chromium binary is not modified. The official image already downloads it during CloakBrowser's own build and is consumed as an upstream dependency. ## Supervisor metadata `stelloauth/config.yaml` will use: - `slug: stelloauth` - `version: 0.1.0` - `arch: [amd64, aarch64]` - `startup: application` - `boot: auto` - `init: true` - no `privileged`, `full_access`, `host_network`, Docker API, devices, or maps - `8080/tcp: null` by default - an HTTP watchdog against `/` on the internal port - no ingress Stelloauth's UI uses absolute paths such as `/oauth` and `/configs`, so it does not work correctly beneath Home Assistant's dynamic ingress prefix without a proxy/rewriting layer. Ingress adds no value to the integration's machine to machine worker call and is omitted from version 0.1.0. ## Networking and Login service URL The custom repository URL is: ```text https://git.radixadm.dk/dennis/homeassistant-stelloauth-addon.git ``` Supervisor derives repository ID `0031621f` from the first eight hexadecimal characters of SHA-1 over the lowercased exact repository URL. With add-on slug `stelloauth`, Home Assistant Core can resolve the add-on as `0031621f-stelloauth` on the internal Supervisor network. The exact Stellantis Vehicles value is: ```text http://0031621f-stelloauth:8080/worker ``` The integration uses this value verbatim and does not append `/worker`. For troubleshooting only, the user may set the disabled host mapping to port 8080 in the add-on Network panel. The corresponding fallback is: ```text http://192.168.1.20:8080/worker ``` The documentation will tell the user to disable the host mapping again after testing. ## Configuration The default configuration works without user input: ```yaml queue_timeout: 60s rate_limit_count: 5 rate_limit_duration: 1h ``` The process manager maps these values to: - `CLOAK_CDP_URL=http://127.0.0.1:9222` - `CLOAK_MAX_SESSIONS=1` - `CLOAK_QUEUE_TIMEOUT=` - `RATE_LIMIT_COUNT=` - `RATE_LIMIT_DURATION=` - `HTTP_ADDRESS=0.0.0.0` - `PORT=8080` - `METRICS_ADDRESS=127.0.0.1` - `METRICS_PORT=9090` `CLOAK_MAX_SESSIONS` is fixed at one because the free CloakBrowser tier allows one concurrent login and the add-on is designed for one household. The queue absorbs overlapping setup/reauth attempts without launching extra browsers. No option contains MyOpel credentials, tokens, cookies, or OAuth codes. ## Security hardening Upstream Stelloauth `v0.6.0` cannot be used unmodified because it logs email addresses and full current/redirect URLs. A redirect URL contains the OAuth code. Its worker also accepts an arbitrary authorize URL, creating a browser-based SSRF surface. The minimal maintained patch will: 1. Remove email addresses from `/oauth` and `/worker` log lines. 2. Replace full redirect/current/error URLs with fixed event messages. 3. Require worker URLs to use HTTPS, the default HTTPS port, and exact host/path combinations for these current Stellantis identity providers: - `idpcvs.citroen.com/am/oauth2/authorize` - `idpcvs.driveds.com/am/oauth2/authorize` - `idpcvs.opel.com/am/oauth2/authorize` - `idpcvs.peugeot.com/am/oauth2/authorize` - `idpcvs.vauxhall.co.uk/am/oauth2/authorize` 4. Limit worker request bodies to 64 KiB. 5. Use the direct remote address for rate limiting instead of trusting a caller-supplied `X-Forwarded-For` header. The CloakBrowser wrapper patch changes the container listener from `0.0.0.0:9222` to `127.0.0.1:9222`. Port 9222 is not declared in Supervisor metadata and cannot be mapped through the add-on UI. Only Stelloauth listens on the Supervisor network. Its port is not mapped to the HAOS host by default. No TLS is required for the worker call because the request stays on the private container network; the subsequent Stellantis traffic uses HTTPS. ## Credential and temporary-data lifecycle Email and password exist only in the incoming request body and in process memory while the OAuth flow runs. They are passed to the page through CDP and are not written to `/data`. Each OAuth attempt uses a unique CloakBrowser fingerprint and profile under `/tmp/cloakserve`. Stelloauth closes the browser target when the flow ends. CloakBrowser runs with `--idle-timeout=30`, and the process manager removes stale `/tmp/cloakserve` contents before startup. Container shutdown terminates CloakBrowser, which closes remaining Chromium processes. No persistent credential, cookie, token, profile, screenshot, HTML dump, or debug dump is created. The only persistent add-on data is Supervisor's `/data/options.json` containing the non-secret options above. ## Startup, readiness, health, and shutdown Startup order: 1. Validate `/data/options.json` and reject malformed values without echoing its content. 2. Remove stale CloakBrowser profiles from `/tmp/cloakserve`. 3. Start `cloakserve --idle-timeout=30`. 4. Wait with bounded backoff for `GET http://127.0.0.1:9222/`. 5. Request `/json/version?fingerprint=addon-readiness` and require a valid `webSocketDebuggerUrl`. 6. Close the readiness profile with `POST /fingerprint/addon-readiness/close`. 7. Start Stelloauth and wait for `GET http://127.0.0.1:8080/`. 8. Enter the process-monitor loop. Startup logs use fixed messages such as `Starting CloakBrowser`, `CloakBrowser ready`, `Starting Stelloauth`, and `Stelloauth listening on 0.0.0.0:8080`. The Supervisor watchdog checks `GET /`. This proves the Stelloauth listener is alive. The process manager supplies the missing combined health property: if CloakBrowser exits, it immediately stops Stelloauth and exits, causing the watchdog/Supervisor restart path to recover both processes together. On SIGTERM or SIGINT, the manager sends SIGTERM to Stelloauth and CloakBrowser, waits up to ten seconds, sends SIGKILL only to remaining children, reaps them, and exits with the original shutdown status. ## Testing and acceptance evidence Automated validation will cover: 1. YAML syntax and required/current Home Assistant metadata fields. 2. Default options matching their schemas. 3. Repository ID and documented internal hostname derivation. 4. Patch application to the exact pinned upstream commits. 5. Unit tests for options, startup ordering, readiness, child failure, signal forwarding, bounded shutdown, and no secret values in manager logs. 6. Stelloauth security tests for URL allowlisting, request-size limits, forwarded-header spoofing, and redacted logs. 7. An amd64 Docker build and container run on the development host. 8. CloakBrowser startup and a real CDP `/json/version` probe. 9. Stelloauth startup and HTTP `GET /` response. 10. `POST /worker` contract behavior with invalid/non-Stellantis URLs, without real MyOpel credentials. 11. Verification from outside the container that port 9222 is unreachable. 12. Graceful stop and restart with no remaining child processes. 13. Log scanning with sentinel email, password, cookie, OAuth code, access token, and refresh token values. 14. Base-image manifest checks for amd64 and arm64 plus an arm64 build when the local Docker builder supports emulation. 15. Idle RAM and image/disk size measurement. Login-flow RAM will be documented as unmeasured unless real credentials are explicitly supplied for testing. Home Assistant's devcontainer/Supervisor validation is preferred when it is available. Standalone Docker tests remain the reproducible minimum. ## Documentation and release maintenance The root README explains repository installation. `stelloauth/DOCS.md` contains the user guide shown by Supervisor: install, start-on-boot, watchdog, log readiness, endpoint test, integration setup, Opel/DK selection, troubleshooting, security model, resource results, and removal. `CHANGELOG.md` starts at `0.1.0` and names both pinned upstream versions. Version updates require changing the pins/digests, reapplying security patches, running the full validation, and reviewing upstream logging, worker validation, license, endpoints, and image manifests before release. ## Known limitations - The first local build is large because CloakBrowser contains Chromium. The target currently has only about 4 GB free, so installation may require disk cleanup after the measured build size is known. - A complete MyOpel login and peak login RAM measurement cannot be performed without real credentials. No credentials will be requested or stored merely to make the build pass. - The fixed allowlist must be updated if the Stellantis Vehicles integration adds or changes identity-provider hosts. - Changing the exact Gitea repository URL changes Supervisor's repository ID, add-on hostname, and Login service URL. - The add-on uses the free CloakBrowser tier and intentionally serializes login flows. ## Out of scope - Installation or deployment to the production Home Assistant host - Docker Compose, Docker-in-Docker, Portainer, host networking, or HAOS system partition changes - Publishing a prebuilt image containing the CloakBrowser binary - Modifying the Stellantis Vehicles integration - Automated use of real MyOpel credentials - Pushing commits to Gitea ## Primary references - - - - - - -