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

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 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:

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:

  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:

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: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