Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/home-assistant/core/llms.txt

Use this file to discover all available pages before exploring further.

Security is paramount in Home Assistant. Integrations must protect user credentials, prevent unauthorized access, and handle sensitive data properly.

Authentication & Credentials

Never Log Credentials

Never log passwords, tokens, or API keys:
import logging

_LOGGER = logging.getLogger(__name__)

# ❌ Wrong: Logs entire config including password
_LOGGER.debug("Config: %s", config)

# ✅ Correct: Redact sensitive data
_LOGGER.debug(
    "Config: username=%s, host=%s",
    config["username"],
    config["host"]
)

# ✅ Also correct: Truncate tokens
token = config["token"]
_LOGGER.debug("Using token: %s...", token[:8])

Secure Credential Storage

Home Assistant encrypts config entry data automatically:
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up from config entry."""
    # Data is encrypted at rest
    username = entry.data["username"]
    password = entry.data["password"]  # Encrypted in storage
    api_key = entry.data["api_key"]    # Encrypted in storage
    
    # Use credentials to create client
    client = await create_client(username, password, api_key)
    return True

OAuth2 Integration

Use OAuth2 for supported services:
from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.helpers.aiohttp_client import async_get_clientsession

class MyOAuth2Implementation(config_entry_oauth2_flow.AbstractOAuth2Implementation):
    """OAuth2 implementation."""
    
    @property
    def name(self) -> str:
        return "My Service"
    
    @property
    def domain(self) -> str:
        return DOMAIN
    
    async def async_generate_authorize_url(self, flow_id: str) -> str:
        """Generate authorization URL."""
        return f"https://api.example.com/oauth/authorize?client_id={self.client_id}"
    
    async def async_resolve_external_data(self, external_data: Any) -> dict:
        """Resolve authorization code to tokens."""
        session = async_get_clientsession(self.hass)
        async with session.post(
            "https://api.example.com/oauth/token",
            data={
                "code": external_data["code"],
                "client_id": self.client_id,
                "client_secret": self.client_secret,
            },
        ) as response:
            return await response.json()

Token Refresh

Handle token expiration gracefully:
from homeassistant.exceptions import ConfigEntryAuthFailed

class MyCoordinator(DataUpdateCoordinator):
    """Coordinator with token refresh."""
    
    async def _async_update_data(self):
        """Fetch data from API."""
        try:
            return await self.client.fetch_data()
        except TokenExpiredError:
            # Try to refresh token
            try:
                await self.client.refresh_token()
                return await self.client.fetch_data()
            except RefreshFailedError as err:
                # Refresh failed - need reauthentication
                raise ConfigEntryAuthFailed(
                    "Token refresh failed, please reauthenticate"
                ) from err

Input Validation

Validate All User Input

Never trust user input:
import voluptuous as vol
from homeassistant.helpers import config_validation as cv

# Use voluptuous for validation
DATA_SCHEMA = vol.Schema({
    vol.Required("host"): cv.string,
    vol.Required("port"): cv.port,
    vol.Required("username"): cv.string,
    vol.Required("password"): cv.string,
    vol.Optional("ssl", default=True): cv.boolean,
})

# In config flow
class MyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    async def async_step_user(self, user_input=None):
        if user_input is not None:
            # Validate input
            try:
                validated = DATA_SCHEMA(user_input)
            except vol.Invalid as err:
                return self.async_show_form(
                    step_id="user",
                    data_schema=DATA_SCHEMA,
                    errors={"base": "invalid_input"},
                )
            
            # Use validated data
            return await self._async_create_entry(validated)

Sanitize File Paths

from pathlib import Path

# ❌ Wrong: Path traversal vulnerability
def load_file(filename: str) -> bytes:
    path = f"/config/files/{filename}"
    with open(path, "rb") as f:
        return f.read()

# User could pass: filename="../../secrets.yaml"

# ✅ Correct: Validate path stays within allowed directory
def load_file(hass: HomeAssistant, filename: str) -> bytes:
    # Ensure filename is safe
    safe_name = Path(filename).name  # Strips directory components
    
    base_dir = Path(hass.config.path("files"))
    file_path = base_dir / safe_name
    
    # Verify path is within base directory
    try:
        file_path.resolve().relative_to(base_dir.resolve())
    except ValueError:
        raise ValueError("Invalid file path")
    
    with open(file_path, "rb") as f:
        return f.read()

Prevent Command Injection

import shlex
import subprocess

# ❌ Wrong: Command injection vulnerability
def run_command(user_input: str):
    subprocess.run(f"ping {user_input}", shell=True)  # Dangerous!

# User could pass: user_input="example.com; rm -rf /"

# ✅ Correct: Use argument list, no shell
def run_command(host: str):
    # Validate input
    if not host.replace(".", "").replace("-", "").isalnum():
        raise ValueError("Invalid host")
    
    # Use argument list, not shell
    subprocess.run(["ping", "-c", "1", host], shell=False)

Network Security

Use HTTPS

Always use HTTPS for API calls:
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import ssl

# ✅ Correct: HTTPS with certificate validation
async def fetch_data(hass: HomeAssistant) -> dict:
    session = async_get_clientsession(hass)
    
    # Default: validates certificates
    async with session.get("https://api.example.com/data") as response:
        return await response.json()

# ❌ Wrong: HTTP or disabled verification
async def fetch_data(hass):
    session = async_get_clientsession(hass, verify_ssl=False)  # Insecure!
    async with session.get("http://api.example.com/data") as response:  # Insecure!
        return await response.json()

Certificate Validation

For self-signed certificates, allow user configuration:
import ssl
import aiohttp
from homeassistant.helpers.aiohttp_client import async_create_clientsession

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up with optional certificate validation."""
    
    if entry.options.get("verify_ssl", True):
        # Default: validate certificates
        session = async_get_clientsession(hass)
    else:
        # User explicitly disabled verification
        # Show warning
        _LOGGER.warning(
            "SSL certificate verification is disabled for %s. "
            "This is insecure and should only be used for testing.",
            entry.title,
        )
        
        connector = aiohttp.TCPConnector(ssl=False)
        session = async_create_clientsession(hass, connector=connector)
    
    client = MyAPIClient(session, entry.data["host"])
    return True

Rate Limiting

Protect against abuse:
import asyncio
from datetime import datetime, timedelta
from collections import deque

class RateLimiter:
    """Rate limiter for API calls."""
    
    def __init__(self, max_calls: int, period: timedelta) -> None:
        """Initialize rate limiter."""
        self.max_calls = max_calls
        self.period = period
        self.calls = deque()
        self._lock = asyncio.Lock()
    
    async def acquire(self) -> None:
        """Acquire permission to make a call."""
        async with self._lock:
            now = datetime.now()
            cutoff = now - self.period
            
            # Remove old calls
            while self.calls and self.calls[0] < cutoff:
                self.calls.popleft()
            
            # Check if we're at limit
            if len(self.calls) >= self.max_calls:
                # Calculate wait time
                oldest = self.calls[0]
                wait = (oldest + self.period - now).total_seconds()
                if wait > 0:
                    await asyncio.sleep(wait)
            
            self.calls.append(now)

# Usage
rate_limiter = RateLimiter(max_calls=10, period=timedelta(minutes=1))

async def api_call():
    await rate_limiter.acquire()
    # Make API call

Code Security

Avoid eval() and exec()

Never use eval() or exec() with user input:
# ❌ Wrong: Arbitrary code execution
def calculate(expression: str) -> float:
    return eval(expression)  # User could pass: "__import__('os').system('rm -rf /')"

# ✅ Correct: Use safe alternatives
import ast
import operator

SAFE_OPERATORS = {
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.Div: operator.truediv,
}

def calculate(expression: str) -> float:
    """Safely evaluate mathematical expression."""
    try:
        tree = ast.parse(expression, mode='eval')
        # Validate only safe operations
        for node in ast.walk(tree):
            if isinstance(node, ast.operator) and type(node) not in SAFE_OPERATORS:
                raise ValueError("Unsafe operation")
        return _eval_node(tree.body)
    except Exception as err:
        raise ValueError(f"Invalid expression: {err}") from err

Secure Deserialization

import json
import pickle

# ❌ Wrong: pickle is unsafe with untrusted data
def load_data(data: bytes):
    return pickle.loads(data)  # Can execute arbitrary code!

# ✅ Correct: Use safe formats like JSON
def load_data(data: str) -> dict:
    return json.loads(data)

# For YAML, use safe_load
import yaml

def load_yaml(data: str) -> dict:
    return yaml.safe_load(data)  # Not yaml.load()!

Dependency Security

Keep dependencies updated:
// manifest.json
{
  "domain": "my_integration",
  "requirements": [
    "my-library==2.1.0"  // Pin versions for security
  ]
}
Regularly check for vulnerabilities:
# Check for known vulnerabilities
pip-audit

# Keep dependencies updated
python3 -m script.gen_requirements_all

Local Network Security

Discover Devices Safely

from homeassistant.components import zeroconf

async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
    """Set up component with device discovery."""
    
    async def async_discover_devices(service_info: zeroconf.ZeroconfServiceInfo):
        """Discover devices on local network."""
        # Validate discovered device before connecting
        if not _is_valid_device(service_info):
            return
        
        # Create config flow for discovered device
        await hass.config_entries.flow.async_init(
            DOMAIN,
            context={"source": config_entries.SOURCE_ZEROCONF},
            data=service_info,
        )
    
    # Register discovery handler
    await zeroconf.async_register_discovery(
        hass,
        ZEROCONF_TYPE,
        async_discover_devices,
    )
    
    return True

Validate Local Connections

import ipaddress

def is_safe_local_address(address: str) -> bool:
    """Check if address is a safe local address."""
    try:
        ip = ipaddress.ip_address(address)
        
        # Allow local addresses
        if ip.is_private or ip.is_loopback:
            return True
        
        # Reject public IPs without explicit user consent
        _LOGGER.warning(
            "Attempted connection to public IP %s, rejecting for security",
            address
        )
        return False
        
    except ValueError:
        # Not an IP address, might be hostname
        return True  # Let DNS resolution handle it

Webhook Security

Validate Webhook Signatures

import hmac
import hashlib
from aiohttp.web import Request, Response

async def handle_webhook(
    hass: HomeAssistant,
    webhook_id: str,
    request: Request,
) -> Response:
    """Handle webhook with signature validation."""
    
    # Get signature from header
    signature = request.headers.get("X-Signature")
    if not signature:
        return Response(text="Missing signature", status=401)
    
    # Get webhook secret
    entry = hass.config_entries.async_get_entry(webhook_id)
    secret = entry.data["webhook_secret"]
    
    # Read request body
    body = await request.read()
    
    # Compute expected signature
    expected = hmac.new(
        secret.encode(),
        body,
        hashlib.sha256
    ).hexdigest()
    
    # Constant-time comparison
    if not hmac.compare_digest(signature, expected):
        _LOGGER.warning("Invalid webhook signature")
        return Response(text="Invalid signature", status=401)
    
    # Process webhook
    return Response(text="OK")

Data Privacy

Respect User Privacy

# Don't collect unnecessary data
class MyEntity(Entity):
    @property
    def extra_state_attributes(self) -> dict:
        return {
            # ✅ OK: Device information
            "model": self.device.model,
            "firmware": self.device.firmware,
            
            # ❌ Wrong: Personal information
            # "user_email": self.device.user_email,
            # "location_history": self.device.locations,
        }

GDPR Compliance

Allow users to export and delete their data:
async def async_remove_config_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
    """Remove config entry and delete all user data."""
    coordinator = entry.runtime_data
    
    # Delete data from cloud service
    await coordinator.client.delete_account_data()
    
    # Clean up local storage
    store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
    await store.async_remove()
    
    _LOGGER.info("All user data has been deleted")

Security Checklist

Before releasing your integration:
  • No credentials in logs
  • All credentials encrypted in config entries
  • Input validation on all user data
  • HTTPS with certificate validation
  • No eval() or exec() with user input
  • Safe deserialization (JSON, not pickle)
  • Rate limiting on API calls
  • Dependencies pinned and audited
  • Webhook signature validation
  • Minimal data collection
  • Data deletion on config entry removal
  • No hardcoded credentials
  • SQL injection prevention (if using SQL)
  • XSS prevention (if generating HTML)

Reporting Security Issues

If you discover a security vulnerability:
  1. Do not open a public issue
  2. Email security@home-assistant.io
  3. Include:
    • Description of vulnerability
    • Steps to reproduce
    • Potential impact
    • Suggested fix (if any)

Resources

Build docs developers (and LLMs) love