Security assurance case

This document is the security assurance case for py-bragerone: it justifies why the project’s security requirements are met. It describes the threat model, identifies trust boundaries, argues that secure design principles are applied, and shows how common implementation security weaknesses are countered.

Threat model

Assets at risk:

  • User credentials (BragerOne account email/password) and the resulting access/refresh tokens, handled at runtime by the library.

  • Integrity of parameter writes sent to heating controllers (a wrong value can change physical heating behavior).

  • Integrity of the library itself (supply chain: dependencies, build and release pipeline).

Threats considered:

  1. Network attacker (MITM) reading or altering API traffic.

  2. Malicious or compromised server responses (REST snapshots, Socket.IO deltas, web-app JS assets) crashing or misleading the client.

  3. Credential/token leakage through logs, diagnostics, or the repository.

  4. Supply-chain attacks via compromised dependencies or a compromised CI/CD pipeline.

  5. Tampered release artifacts (PyPI/GitHub Releases).

Trust boundaries

  1. Library ↔ BragerOne cloud API — all data crossing this boundary is untrusted input: the primary REST DTOs (auth, objects, modules) are validated with pydantic models, while some snapshot/prime endpoints deliberately return raw data for flexibility; live JS assets are always parsed defensively (tree-sitter, no code execution, soft-fail on unexpected shapes).

  2. Library ↔ host application (e.g. Home Assistant integration) — the public API (BragerOneApiClient, BragerOneGateway) is the only intended surface; internal modules are not re-exported.

  3. Repository/CI ↔ package publishing — PyPI publishes only from the tag-triggered release workflow via OIDC trusted publishing (no stored PyPI token). Stable CalVer tags are refused unless the tagged commit is on origin/main; pre-releases (aN / bN / rcN) may also be cut from release/* trains. Separately, the docs workflow may deploy GitHub Pages (pages: write scoped to documentation).

Secure design principles applied

  • Secure defaults: TLS certificate verification is on by default (httpx verify=True); it is never disabled internally — a caller may only weaken it explicitly via the public verify parameter, e.g. for local diagnostics. Tokens auto-refresh without caller involvement.

  • Least privilege: every workflow declares explicit permissions: (default contents: read). PR-triggered jobs have no access to long-lived secrets (none are stored for CI); they use only the short-lived, per-run GITHUB_TOKEN. Write scopes are granted narrowly: pull-requests: write (+ contents: write only in the Dependabot auto-merge job) and security-events: write in CodeQL for uploading results. Package-publishing rights exist only in the tag-triggered release workflow.

  • Fail-safe defaults: parsers return partial results or None instead of raising on unexpected upstream shapes; a new upstream bundle must not crash the library or dependent HA setups.

  • Economy of mechanism: the library is a thin async client; protocol complexity is isolated in api/ and models/ with a small public API.

  • Mediation on writes: the CLI checks numeric writes against the catalog’s min/max range and applies the numeric transform before dispatch; nonnumeric values are passed through without range checks, and enum handling is left to callers. The low-level BragerOneApiClient write methods are intentionally a thin raw transport; callers building on them (e.g. the Home Assistant integration) are expected to implement the full validation layer (enum label→raw, inverse transform, range/route checks), and the integration does.

Countering common implementation weaknesses

  • Injection: no eval/exec and no shell invocation on untrusted runtime data; JavaScript assets are parsed with tree-sitter, never executed. (One deliberate exception: the Sphinx build exec``es a trusted, repo-generated version file in ``docs/conf.py.)

  • Secrets exposure: in-repo controls: gitleaks runs in CI and pre-commit, and nothing sensitive is stored in the repository. The CLI takes account credentials from --email/--password or the PYBO_EMAIL/PYBO_PASSWORD environment variables, and prompts securely (hidden getpass input) when the password is omitted in an interactive terminal — argv should be avoided, as it can leak via shell history and process inspection. Its token store keeps the resulting access/refresh tokens in the system keyring with a file fallback. Log/diagnostic redaction is available and configurable. As a hosting-layer control, the GitHub repository additionally has secret scanning with push protection enabled.

  • Broken crypto: the library implements no cryptography itself; transport security is delegated to Python’s TLS stack (TLS 1.2+).

  • Memory safety: the library itself is pure Python, but it relies on the native tree-sitter/tree-sitter-javascript extensions for asset parsing; that native boundary handles untrusted JS bytes and is covered by the parser-resilience test suite (malformed-shape fixtures) and by the catalog fuzz harness (fuzz/fuzz_catalog.py).

  • Vulnerable dependencies: dependencies are declared in pyproject.toml and pinned in uv.lock; Dependabot and Renovate keep them current; pip-audit scans them in CI; security exceptions (if any) are documented and tracked in SECURITY.md.

  • Static/dynamic analysis: CodeQL and pip-audit run in GitHub Actions; bandit and Ruff flake8-bandit (S) rules run in pre-commit hooks and via the local poe security / poe lint tasks; mypy --strict and ruff enforce type and code discipline; a fuzz harness (atheris) plus Hypothesis property tests exercise the parsers.

  • Release integrity: releases are built in CI from the tagged commit and ship with SHA256 checksums, a CycloneDX SBOM, and Sigstore build-provenance attestations verifiable with gh attestation verify.

Residual risk

The library depends on the undocumented, evolving BragerOne cloud API and web assets; upstream changes can break parsing (mitigated by defensive parsing and test coverage) and the service’s own security is outside this project’s control. The CLI still accepts a password via --password argv, which can persist in shell history and process listings; prefer the PYBO_PASSWORD environment variable or the interactive prompt.