14 KiB
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
ovaon 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:
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
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:
- A pinned Go builder clones the Stelloauth
v0.6.0tag, verifies that HEAD is exactly367d4f8c02a3b072c59142c49dffc129edc8548b, applies the maintained security patch, runs the relevant Go tests, and builds a static binary. - The runtime stage derives from the official pinned CloakBrowser
0.5.10image, 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: stelloauthversion: 0.1.0arch: [amd64, aarch64]startup: applicationboot: autoinit: true- no
privileged,full_access,host_network, Docker API, devices, or maps 8080/tcp: nullby 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:
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:
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:
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:
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:9222CLOAK_MAX_SESSIONS=1CLOAK_QUEUE_TIMEOUT=<queue_timeout>RATE_LIMIT_COUNT=<rate_limit_count>RATE_LIMIT_DURATION=<rate_limit_duration>HTTP_ADDRESS=0.0.0.0PORT=8080METRICS_ADDRESS=127.0.0.1METRICS_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:
- Remove email addresses from
/oauthand/workerlog lines. - Replace full redirect/current/error URLs with fixed event messages.
- 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/authorizeidpcvs.driveds.com/am/oauth2/authorizeidpcvs.opel.com/am/oauth2/authorizeidpcvs.peugeot.com/am/oauth2/authorizeidpcvs.vauxhall.co.uk/am/oauth2/authorize
- Limit worker request bodies to 64 KiB.
- Use the direct remote address for rate limiting instead of trusting a
caller-supplied
X-Forwarded-Forheader.
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:
- Validate
/data/options.jsonand reject malformed values without echoing its content. - Remove stale CloakBrowser profiles from
/tmp/cloakserve. - Start
cloakserve --idle-timeout=30. - Wait with bounded backoff for
GET http://127.0.0.1:9222/. - Request
/json/version?fingerprint=addon-readinessand require a validwebSocketDebuggerUrl. - Close the readiness profile with
POST /fingerprint/addon-readiness/close. - Start Stelloauth and wait for
GET http://127.0.0.1:8080/. - 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:
- YAML syntax and required/current Home Assistant metadata fields.
- Default options matching their schemas.
- Repository ID and documented internal hostname derivation.
- Patch application to the exact pinned upstream commits.
- Unit tests for options, startup ordering, readiness, child failure, signal forwarding, bounded shutdown, and no secret values in manager logs.
- Stelloauth security tests for URL allowlisting, request-size limits, forwarded-header spoofing, and redacted logs.
- An amd64 Docker build and container run on the development host.
- CloakBrowser startup and a real CDP
/json/versionprobe. - Stelloauth startup and HTTP
GET /response. POST /workercontract behavior with invalid/non-Stellantis URLs, without real MyOpel credentials.- Verification from outside the container that port 9222 is unreachable.
- Graceful stop and restart with no remaining child processes.
- Log scanning with sentinel email, password, cookie, OAuth code, access token, and refresh token values.
- Base-image manifest checks for amd64 and arm64 plus an arm64 build when the local Docker builder supports emulation.
- 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/