Files
homeassistant-stelloauth-addon/docs/superpowers/specs/2026-09-24-home-assistant-stelloauth-addon-design.md
T

364 lines
14 KiB
Markdown

# 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=<queue_timeout>`
- `RATE_LIMIT_COUNT=<rate_limit_count>`
- `RATE_LIMIT_DURATION=<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
- <https://github.com/tamcore/stelloauth/tree/v0.6.0>
- <https://github.com/CloakHQ/CloakBrowser/tree/v0.5.10>
- <https://github.com/andreadegiovine/homeassistant-stellantis-vehicles/releases/tag/2026.9.4>
- <https://developers.home-assistant.io/docs/apps/configuration/>
- <https://developers.home-assistant.io/docs/apps/communication/>
- <https://developers.home-assistant.io/docs/apps/repository/>
- <https://developers.home-assistant.io/docs/apps/testing/>