# Security Tenant isolation is enforced at several layers, all inside `saas.provisioner`. ## Database isolation Every Odoo container/pod is started with: ```text --db-filter ^$ --no-database-list ``` This restricts the process to exactly one database and disables Odoo's database selector/manager UI entirely — a tenant's container can never even list, let alone touch, another tenant's database, regardless of anything that happens at the application layer above it. ## Per-tenant master password Each tenant gets its own randomly generated `admin_passwd` (`secrets.token_urlsafe(32)`), written into an `odoo.conf` the container mounts read-only: - **Kubernetes** — stored as a Kubernetes Secret (`odoo-security-`), mounted at `/etc/odoo/odoo.conf`. Created once; **not regenerated on redeploys** so the master password stays stable across restarts/reprovisions. - **Docker** — written to a host-side config file under `DOCKER_TENANT_CONFIG_BASE_PATH//odoo.conf`, `chmod 600`, owned by the Odoo UID/GID, and bind-mounted read-only into the container. The password is written straight to that file and never passed as a container command-line argument (command-line args are visible to anything that can run `docker inspect`/`ps` on the host). ## Namespace / network isolation - **Kubernetes** — each tenant gets its own namespace (`saas-`), labeled `saas.dishonkadoh.com/managed=true` and `saas.dishonkadoh.com/instance=` for auditing. Pods run as a fixed non-root UID/GID (100/101) via a pod security context (`run_as_user`/`run_as_group`/`fs_group`). - **Docker** — all tenant containers share one bridge network (`saas-network` by default), isolated from each other only by the `--db-filter` restriction above rather than network segmentation — worth knowing if you're reasoning about the Docker backend's isolation properties versus the Kubernetes one. ## Storage isolation Filestore and custom-addons live on **separate** volumes per tenant (two PVCs on Kubernetes, two bind-mounted directories on Docker) — deliberately split so application code and tenant-generated data have independent lifecycles. See [Architecture § Custom addons](../developer/architecture.md) for why addon code and Odoo data are never mixed on the same volume. ## Command redaction in logs Any pod-exec/container-exec command containing `--db_password` or `--password` has the following argument redacted before being logged (`_redact_command`) — worth knowing if you're grepping provisioning logs for a failed command and the password shows up as `***REDACTED***` instead of the real value; that's intentional. ## Custom addon deployment path traversal checks Before any addon is deployed via the Git workflow, its technical name and addon path go through explicit validation (`deploy_custom_module_to_instance` / `deploy_addon_to_instance`): technical names must match `^[A-Za-z0-9_][A-Za-z0-9_.-]*$`, and the resolved addon path is checked against `../`, `./`, and any path-escape attempt relative to the cloned repository root before anything is copied into tenant storage.